I have two questions about ApplicativeDo extension.
- 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?
- 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
-
the outer do-block which we have in script definition, e.g. testAction = script do
, or let archiveAccounts p = do
, and
-
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 !!