One way to think about actions in Daml is as little programs that run “within” the ledger. From that perspective, the entire “program” is run atomically, and can modify the ledger (ultimately by adding create and archive lines at the end).
From that (simplistic) perspective:
- An applicative action is one in which the client knows, in advance, exactly the lines that need to be written to the ledger. There’s still a chance the action can fail, if for example a contract you want to archive has already been archived. But you could think of applicative actions as being purely a list of lines to add “as is” at the end of the ledger (assuming they’re compatible with the state of the ledger at the time the transaction gets executed).
- A monadic action is one in which, instead of sending “pure data” to the ledger, you send a “function” (not actually a Daml function; actually represented as data, but bear with me) that will be run “inside” the ledger in order to generate the lines to add. That means that a monadic action can do something like fetch a contract, look at its payload, and then, based on that, decide whether or not to archive it.
Hopefully that helps clarify things a bit.
Of course, as you know, it’s not quite correct to say that an action “is” monadic or applicative. In reality, an action is an action. If you have two actions, you can compose them in either a monadic or an applicative way, but in both cases the result is an action. So you could compose applicatively two actions that happen to both “be monadic inside”; in that case, the applicative composition expresses that there is no need for a transfer of information from the first to the second while the transaction is executing “inside” the ledger.
In the propose/accept pattern, as @cocreature illustrated, the
Accept choice needs to do two things: archive one contract, and create a new one, and both things are “fully known” client-side and don’t depend on each other, so you can use applicative composition. (Note that this does not mean they are necessarily commutative: in this case they are, but if we were to archive-then-recreate a contract for which the template defined a key, they would not be.)
When would we need a monadic composition? Let us imagine the owner of the
T contract wanted to verify the citizenship of the new owner before executing the transfer. The
Accept choice would then take as an argument a ContractId for a contract signed by the government. It could look something like:
module Main where
proposer : Party
accepter : Party
signatory proposer, accepter
t : T
nonconsuming choice Accept : ContractId T
with cit: ContractId Citizenship
do cit <- fetch cit
if cit.gov `elem` acceptedGovs then
archive self *> create t
fail "Wrong citizenship"
setup : Script ()
setup = script do
alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
wonderland <- allocatePartyWithHint "Wonderland" (PartyIdHint "Wonderland")
prop <- submit alice do
createCmd TProposal with t = (T alice bob), acceptedGovs = [wonderland]
cit <- submit wonderland do
createCmd Citizenship with gov = wonderland, citizen = bob
submit bob do
exerciseCmd prop Accept with cit
Accept, we now need to fetch a contract from the ledger, which in a transactional system we want to do at transaction time, i.e. we want this to happen “within the ledger”, not just done by the client ahead of time. (In practice the client does it ahead of time and then the ledger verifies it, but let’s gloss over that here.)
The crucial difference is that in @cocreature’s version, we don’t need to use any information resulting from the
archive call in the
create call, whereas here we do want to make a decision (
if/else, but that could just as well be “include the value of this attribute”) based on the result of executing the
fetch action. (There’s also of course the entire
setup script, which has to be monadic because we want to use
Accept call etc.)
Hopefully this has clarified things, not made them more confusing.