ApplicativeDo

I have two questions about ApplicativeDo extension.

  1. In the archiveAccounts function shown in the code below, I am using a, b, and d coming from previous action statements. Everything works as expected with the “{-# LANGUAGE ApplicativeDo #-}” extension at the top. If removed, the second do-block in archiveAccounts function gives error “No instance for (Action Commands)…” From this I understand that the ApplicativeDo extension is required when we have more than one submit and where the next submit is dependent on the result of the previous one.

But the docs say the opposite

if you enable the ApplicativeDo extension by adding {-# LANGUAGE ApplicativeDo #-} at the top of your file, you can use do-notation but the individual commands must not depend on each other

So looks like my interpretation is incorrect. Can you please help?

  1. I tried putting one more command “archiveCmd c” right after exerciseCmd. But this gives “No instance…” error even with ApplicativeDo extension. This tells me that ApplicativeDo does not work in nested do-blocks. Is that correct?

Here is the code to set the context:

module BankServiceExample where 

template BankAccount 
    with
       bank: Party
       accountOwner: Party
       balance: Numeric 2
    where
        signatory bank, accountOwner

        choice DeductFee: ContractId BankAccount 
            with fee: Numeric 2 
            controller bank
                do 
                    create this with 
                        balance = balance - fee
{-# LANGUAGE ApplicativeDo #-}

module ApplicativeDoExample where 

import Daml.Script
import BankServiceExample

testAction: Script () 
testAction = script do 

    alice <- allocateParty "Alice"
    bob <- allocateParty "Bob"
    bank <- allocateParty "bank"

    submitMulti [alice, bank][] do 
        createCmd BankAccount with 
            bank 
            accountOwner = alice
            balance = 100.0

    submitMulti [alice, bank][] do 
        createCmd BankAccount with 
            bank 
            accountOwner = alice
            balance = 50.0

    submitMulti [bob, bank][] do 
        createCmd BankAccount with 
            bank 
            accountOwner = bob
            balance = 70.0


    -- archiveAccounts queries for a party's BankAccounts, Deduct's fee, and then archives that account
    let archiveAccounts p = do
        a <- query @BankAccount p  -- get party p's accounts 
        
        b <- forA a (\x -> submitMulti [bank, p][] do -- for each account, deduct fee
            c <- exerciseCmd x._1 DeductFee 
                with fee = 10.0 
            -- archiveCmd c  --this fails "No instance for (Action Commands) arising from a do statement"
            return (c))
        
        d <- forA b (\x -> submitMulti [bank, p][] do 
            archiveCmd (x))

        return()

    forA [alice, bob] (\x -> archiveAccounts x) -- archive accounts owned by parties in [alice, bob]

    return ()

@Neelam_Dwivedi
The ApplicativeDo extension is not required to use multiple submit functions in a single do block, even if the next submit is dependent on the result of the previous. E.g. if in your archiveAccounts function you replace

        b <- forA a (\x -> submitMulti [bank, p][] do -- for each account, deduct fee
            c <- exerciseCmd x._1 DeductFee 
                with fee = 10.0 
            -- archiveCmd c  --this fails "No instance for (Action Commands) arising from a do statement"
            return (c))

with

        b <- forA a (\x -> submitMulti [bank, p][] do
            exerciseCmd x._1 DeductFee with fee = 10.0)

your example will work without ApplicativeDo, even though the submits in the forA that maps to variable named d depend on the variable named b that is mapped to previous forA with other submits.
The ApplicativeDo extension is required if you want to use multiple commands in the do block in a single submit. In this case the commands within the single submit must not depend on one another. In your example archiveCmd c depends on the result of exerciseCmd x._1 DeductFee with fee = 10.0 within the same submit in the forA mapped to variable b. This is why uncommenting archiveCmd c produces “No instance for (Action Commands)…” compile error.
What’s a bit confusing is that in your original example with archiveCmd c commented out there’s only one command submitted to the ledger in submitMulti. The last line in the do block is return c, which is not a command. It’s an expression, and this is why you can use the result of the previous command in it. But even though return c is not a command, the compiler produces “No instance for (Action Commands)…” error without ApplicativeDo. I guess this could be considered a deficiency or a quirk in the compiler.
A good example of a use case for ApplicativeDo extension is available in the documentation for Daml Script.

Thanks @a_putkov !

This makes sense! I went back to docs and noticed the statement just before the statement I quoted

This is used to build up the commands send as part of submit

Missing this statement coupled with some confusing error messages sent me on the wrong track!

Greatly appreciate your detailed response!

@a_putkov

Just to make sure I got this right, I understand that there is a difference between

  1. the outer do-block which we have in script definition, e.g. testAction = script do , or let archiveAccounts p = do, and

  2. the ‘do’ inside a submit statement

The first one is the syntactic sugar for bind-operator >>= coming from

class Applicative m => Action m where 
(>>=) : m a -> (a -> m b) -> m b

The second one is just a do-block and is not an alternate form of >>=. Is that understanding correct?

No, I don’t think this is correct. I don’t see any difference between do notations in these cases. And I don’t see any connection between do keyword and sequential composition of actions function (>>=). The latter takes two actions and requires that the result of the first action is used as an argument to the second. The do notation allows you to group any number of Update expressions regardless of whether the result of any previous Update is used in any subsequent one. With the caveat that, if the do block is used in the second argument of the submit function, then the commands in the do block must not have any dependencies on one another. And that, if do block in the second argument of the submit function has more than one command, then you need ApplicativeDo extension. Both of these caveats are imposed by the Commands type used in the second argument of the submit function, not by the do keyword.
I find that the best explanation of the do notation is given in the reference documentation for Updates.

OK, then I probably made wrong connections from Haskell to Daml.

Per this reference,

It’s important to remember that do expressions are just different syntax for chaining monadic values.

I never thought about it this way, but now that you said it, I think your original statements are actually correct. Indeed, the do notation outside of the submit function can be represented by successive application of (>>=) function to the first action in the do block and a series of nested lambda functions, where the result of the action from the outer lambda is passed as an argument to the inner lambda. The archiveAccounts function from your example can be rewritten as

    let archiveAccounts p = 
            query @BankAccount p >>=        
                (\a -> forA a (\x -> submitMulti [bank, p][]
                    $ exerciseCmd x._1 DeductFee with fee = 10.0) >>=
                    (\b -> forA b (\x -> submitMulti [bank, p][]
                        $ archiveCmd x)))

And when the do notation is used in the second argument of the submit function, it is indeed different in the sense that the commands inside the do block cannot be chained using (>>=), as the (>>=) function can only be applied to actions (in other words to instances of the Action typeclass), while the Commands type required for the second argument of the submit function (and returned by createCmd, exerciseCmd etc.) is not an instance of the Action typeclass, hence the error “No instance for (Action Commands) arising from…”

1 Like

Thanks @a_putkov ! The code you wrote for archiveAccounts using >>= is going straight into my notes !! :star_struck: