The relationship between applicative/monadic action composition and the implementation of contract law

I want to understand the relationship between monadic action composition and the propose/accept pattern, which is the cornerstone of the implementation of contract law.

The following slides reflect my current understanding. Please correct me if I’m missing something.

Update: replaced the second slide by two new slides based on the comments made by @cocreature and @Gary_Verhaegen.

1 Like

I’m not quite sure I understand your question. It looks like you are only looking at the Accept choice. You can implement that using only applicative operators:

To make the archival explicit, I made accept a non-consuming choice

template T
  with
    proposer : Party
    accepter : Party
  where
    signatory proposer, accepter

template TProposal
  with
    t : T
  where
    signatory t.proposer
    observer t.accepter
    nonconsuming choice Accept : ContractId T
      controller t.accepter
      do archive self *> create t
1 Like

Yes, that’s right. exerciseByKey would be a better example for the usefulness of monadic composition I guess. What do you thin?

Can you expand on your thinking here? It isn’t clear to me how an individual function exemplifies monadic composition.

Hi @gyorgybalazsi,

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

import Daml.Script

template T
  with
    proposer : Party
    accepter : Party
  where
    signatory proposer, accepter

template Citizenship
  with
    gov: Party
    citizen: Party
  where
    signatory gov
    observer citizen

template TProposal
  with
    t : T
    acceptedGovs: [Party]
  where
    signatory t.proposer
    observer t.accepter
    nonconsuming choice Accept : ContractId T
      with cit: ContractId Citizenship
      controller t.accepter
      do cit <- fetch cit
         if cit.gov `elem` acceptedGovs then
           archive self *> create t
         else
           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

  return ()

In 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 prop in bob's Accept call etc.)

Hopefully this has clarified things, not made them more confusing.

2 Likes

Thanks, updated the slides based on your answers.