Hi @krmmalik,
I’ll address one point at a time.
preconsuming choice Agree: ContractId ProposeGuild
Here we are setting up a choice agree
and setting it up for the ProposeGuild contract, yes?
You can think of choices as functions acting on templates. Here we declare a new choice called Agree
that acts on contracts of type ProposeGuild
and has a return value of type ContractId ProposeGuild
. In other words, when you later on write something like:
ret <- submit party do exerciseCmd proposeCid Agree with ..
the type of ret
will be ContractId ProposeGuild
.
I wasn’t sure why it was a preconsuming
choice however.
A choice can have one of four qualifiers:
preconsuming
archives the contract before the choice code is executed; this means that the choice code cannot fetch
the current contract, because it has already been archived when the code runs. Note that this does not prevent you from accessing the payload (attributes) of the current contract, as those are still in scope.
postconsuming
archives the contract after the choice has finished running. This means the choice code can fetch
the contract if it needs to, but it also means you cannot create a new contract with the same key (because the current one has not been archived yet).
nonconsuming
means the Daml engine will not automatically archive the contract for you. Note that this does not always mean that the choice does not archive the contract, as the choice code can still call archive self
explicitly. This can be very useful if you want to do something before and after archiving, or if you only want to archive if some conditions are met.
The default behaviour, when no qualifier is present before the choice
keyword, is what is known as a consuming choice in the documentation. Consuming choices have additional properties with respect to privacy that I do not fully understand, and therefore I tend to avoid them. This is why I default to preconsuming
unless I have a good reason to use one of the others. In this specific case I think all three options (postconsuming
, preconsuming
and consuming) that end up archiving the contract would work equally well.
with member: Party
member
doesn’t appear in the ProposeGuild or
Guild` template, so why do we set it up new here? Why not requestedMember or agreedmember? is it because he/she is neither agreed nor requested at this stage?
This with ...
syntax is defining the parameters of the choice, which you can think of as the function arguments. When you exercise the choice, you need to supply them; in this case, in order to call the choice with exerciseCmd
, you will need to supply a party. We do not want to reuse a name that already exists here specifically because we want to be able to compare this member
party provided by the caller of the API (who would be able to pass in whatever they want) with the parties saved in the contract (agreedMembers
and requestedMembers
).
assert $ member `elem` requestedMembers
I hardly understood this line at all. Here we are asserting that the member will be mapped into requestedMember?
Not quite. assert
is a special operation that takes one Boolean argument (True
or False
) and does nothing if it is True
. In this case, the expression:
member `elem` requestedMember
is True
if the member
supplied by the person calling the API to exercise
the Agree
choice is present in the requestedMember
list supplied by the person who created the ProposeGuild
contract, and False
if member
is not an element of the requestedMembers
list.
If the argument to assert
is False
, the Daml system interrupts the execution and cancels the current transaction, without having made any change to the ledger. So this line can be read as “proceed only if the person who is currently accepting is someone who has been asked and has not responded yet”.
create this with agreedMembers = member::agreedMembers
Why are we using this ?
The create
function takes a payload for a contract and creates a corresponding entry on the ledger. The syntax for creating records (contract payloads are always records) in Daml is fairly varied. Assuming a record defined by:
data MyRecord = MyRecord with a: Int, b: Text
which, as you may recall, can be used directly in Daml, but is also what would be generated for a template:
template MyRecord
with
a: Int
b: Text
where
...
the most direct way to create a record of that type would be:
let mr = MyRecord with a = 1, b = "hello"
But if you already have one, there is a convenient notation to copy it then change only a few keys:
let mr2 = mr with a = 3 -- same as MyRecord with a = 3, b = "hello"
Getting back to our use-case, in the code of a choice you always have access to the special this
variable that represents the payload of the contract the choice is being exercised on. So in this case the syntax:
this with agreedMembers = member::agreedMembers
requestedMembers = DA.List.Delete member requestedMembers
is just a shorthand for:
ProposeGuild with guildName = guildName
creator = creator
agreedMembers = member::agreedMembers
requestedMembers = DA.List.Delete member
Did I mention that the syntax to create records is very varied? Both of these would also have been equivalent in this context:
ProposeGuild with guildName -- if there is no "=", use the value with the
creator -- same name in the current scope
agreedMembers = member::agreedMembers
requestedMembers = DA.List.Delete member
ProposeGuild with agreedMembers = member::agreedMembers
requestedMembers = DA.List.Delete member
.. -- .. means "use values from scope" for
-- all values that have the same name as a field
You may also have been confused by the expressions member::agreedMembers
and DA.List.delete member requestedMembers
. The first one,
member :: agreedMembers
is using the “cons” operator ::
to construct a list. You can see this in operation at the repl:
$ daml repl
daml> let a = [1, 2]
daml> 3 :: a
[3,1,2]
daml>
The function DA.List.delete elem list
returns a new list that has the same elements as list
except for the first occurrence of elem
. Back to the repl:
daml> import DA.List
daml> DA.List.delete 1 [2, 3, 4]
[2,3,4]
daml> DA.List.delete 4 [2, 3, 4]
[2,3]
daml> DA.List.delete 4 [2, 3, 4, 5, 4]
[2,3,5,4]
daml>
One important line you did not ask about is:
controller member
This tells Daml that only the party member
is allowed to exercise this choice. In this case, the combination of these two lines:
with member: Party
controller member
means that the person (party) exercising the choice has to pass themselves as a parameter, as in:
propId2 <- submit rita do
exerciseCmd propId1 Agree with member = rita
In many cases the controller
of a choice will be a single, fixed, known party identified directly on the contract (say, in this case, creator
). But in some cases (like here) it is useful to make the controller of a choice an explicit parameter of that choice. Daml will ensure that only the party itself can submit that choice (i.e. Rita could not do exerciseCmd propId2 Agree with member = john
), but we can then add more constraints on it if we want. In this case, it is useful to restrict further to only people who are invited.
Hope that helps clarify some of your questions; do not hesitate to ask more if needed.