If-Then-Else statements inside forA loop in DAML
Hi, Sorry for Newbie question, but how do I write a if-then-else in forA loop in DAML.
Also, if block returns a set of variables and else returns a set of variables. Only after I finish this if else and the forA and get all the variables, I should be able to create a contract by passing all these variables.
P.S. if-then-else is inside forA loop.
Hi @Pris17,
I’m not exactly sure what the problem is here, as you can write the code as usual within the forA loop block:
forA [alice, bob, charlie] $ \p ->
if isCoolParty p then
// do something
pure ()
else
// do something
pure ()
This would return an Update [()].
EDIT:
If you’re unsure how to use a feature I recommend the cheatsheet
Thanks @victor.pr.mueller . My code looks like.
controller PartyA can
nonconsuming CreateContractA : ContractId ContractA
with
otherParties : [Party]
do
mcID <- lookupByKey @ContractA (PartyA, id)
case (mcID) of
Some mcId -> return mcId
None ->
do
do forA otherParties (\dv ->
do
(cID, dvContract) <- fetchByKey @ContractB (dv, id)
if (show dv == "PartyC") then do
let a = dvContract.abc
b = dvContract.def
else if (show dv == "PartyD") then do
let c = devSecCon.ghi
d = devSecCon.jkl
)
create ContractA with a= a; b= b; c= c; d= d; ..
But this throws error. “parse error (possibly incorrect indentation or mismatched brackets)parser”…Can you help me indent this code and also rectify the other mistakes
@bernhard Can you please help me fix the above code?
The indentation of if..then..else should be
if condition
then do
something
else do
something_else
You currently have the if and else at the same level of indentation. So see whether indenting these three lines does the trick:
else if (show dv == "PartyD") then do
let c = devSecCon.ghi
d = devSecCon.jkl
Both versions of indentation are fine to use so far I see (tested locally).
Ok, I was shooting from the hip. There are actually a number of issues here including if..then without else and do blocks that contain only let bindings.
Before I can help you fix this, I need to understand what you are trying to do. You have this line create ContractA with a= a; b= b; c= c; d= d; .., which means you need to define values a, b, c, and d. But you are trying to define them conditionally, which means that depending on show dv, these values are not being defined.
Could you explain in pseudo-code or words, what logic you are trying to express?
So far I see from your code you need to do things differently. You cannot use bindings which have been declared in a scope which is not reachable within the current scope. For this case this means that the declarations within the lambda of the forA otherParties statement are not available outside of it.
Besides this, your contract creation currently requires that at two specific parties exist but your party list doesn’t seem like it has constraints which gurantee that these parties will exist in the list (neither does your code check beforehand).
Furthermore, if you’re matching on the party names I suggest that you
- Use pattern matching instead of
if-elsestatements - Use
partyToTextfor converting a party to a String/Text
Pattern matching can be used as follows:
case partyToText dv of
"PartyC" -> ...
"PartyD" -> ...
_ -> ... -- handle the case that the party name is none of the above
Only after I finish this if else and the forA and get all the variables, I should be able to create a contract by passing all these variables.
There are ways to deal with this, but I think forA should not be used at all in this situation.
You cannot use bindings which have been declared in a scope which is not reachable within the current scope.
The way that some languages think of variables is dynamically assigned. That is, your create ContractA line depends on the preceding let syntax having updated this sort of invisible map whose keys are variable names and whose values are the values of those variables. Suppose that the loop never encountered cases that match the if conditions, and therefore never assigned a, b, c, or d? Such languages just hope for the best and (ideally, but probably don’t) prepare for the worst.
Daml treats variables as statically bound, though. If you are permitted to use a variable a of type T, every reference to that variable will absolutely succeed by yielding a value of type T. You cannot write code that hopefully binds some variables; either the code is guaranteed to bind the variable, or is guaranteed to not bind the variable.
There are ways to arrange things in your code such that a, b, c, d values (not the variables) “get out” of the forA. But this is needlessly complex for your specific use case, with all sorts of extra failure cases and states to handle. Instead, here is how I would arrange things, and it can all be done in the single do block established for your None case:
- Use
partyFromText "PartyC", invokingfromSomeNoteon that to get aParty-typed value for"PartyC". Bind it to local variablepartyC. - (Optionally) assert that
elem partyC otherParties. Since the apparent intention of your code is to abort transaction if any of the parties you need aren’t present inotherParties, doing this will preserve that intention, but maybe you didn’t really mean that. - Use
fetchByKeyjust as you currently do to get thedvContract. - Bind your
abc,deffields from that contract toaandb, usingletjust as you do now. - Repeat steps 1-4 for every other party for which you need to fetch a contract and extract values to local variables.
- At the end of the
do, yourcreate ContractAwill have every variable you need in scope, and guaranteed to be present if you have gotten this far.
To call out specifically which things are not used at all in this approach:
- there is no
forA - there is no
if(though we do useelemandassert, if that is your intention) - there is no nested
do
Hi, @Stephen If I understood correctly, below is the updated code as per your approach. I still get errors in this code. Let me know if I have missed something.
controller PartyA can
nonconsuming CreateContractA : ContractId ContractA
with
otherParties : [Party]
do
mcID <- lookupByKey @ContractA (PartyA, id)
case (mcID) of
Some mcId -> return mcId
None ->
do forA otherParties $ \dv ->
case partyToText dv of
"DV1" ->
do
(cID1, dvSecCon1) <- fetchByKey @ContractB (dv, id)
let a = dvSecCon1.abc
b = dvSecCon1.def
"DV2" ->
do
(cID2, dvSecCon2) <- fetchByKey @ContractB (dv, id)
let c = dvSecCon2.ghi
d = dvSecCon2.jkl
create ContractA with a=a; b=b; c=c; d=d; ..
It throws an error on the last Create ContractA line. The error is
Parse error in pattern: createparser
Hi @Pris17, the error mentions createparser which isn’t part of the code snippet you showed so it looks like it comes from a different piece of code. Would it be possible to share your full code or a minimized version of that?
Hi @Pris17,
First off, for better or worse, the Daml language is “whitespace-sensitive”, which in this case means that indentation is used to delimit blocks of code. Lines of code that are supposed to be part of the same block must start at the same offset (i.e. be preceded by the same number of spaces), and nested blocks must be indented more than their enclosing block.
So, for example:
let c = 1
d = 2
is valid, but
let c = 1
d = 2
is not because c and d need to be aligned.
Another example:
let i = case mi of
None -> 0
Some x -> x
is valid, but
let i = case mi of
None -> 0
Some x -> x
would not be, because the None and Some lines are supposed to start nested blocks and thus need to be indented more than the i.
Daml does not have variables; what it does have is bindings. A binding is a way for an enclosing block to define a shorthand name to be used in a nested block. Bindings can never escape their enclosing block.
The one exception to this is do notation, which is special in that it allows one to define bindings within the current block, to be used at any point after their definition (but still only within the current block, i.e. anything as indented or more indented than the line on which the binding is introduced).
Constructs that introduce new bindings also introduce nested blocks where those bindings are defined. For example, the let ... in construct:
fib : Int -> Int
fib n = case n of
0 -> 0
1 -> 1
n -> if n < 0
then 0
else let n1 = fib (n - 1)
in let n2 = fib (n - 2)
in n1 + n2
defines a binding that can only get used in the block introduced by in. In a way, you can imagine that you have parentheses (and this is in fact valid Daml) like so:
fib : Int -> Int
fib n = case n of
0 -> 0
1 -> 1
n -> if n < 0
then 0
else (let n1 = fib (n - 1)
in (let n2 = fib (n - 2)
in (n1 + n2)))
and that the binding defined by let is only available within the set of parentheses that being immediately after in.
So, in your code sample:
do forA otherParties (\dv ->
do
(cID, dvContract) <- fetchByKey @ContractB (dv, id)
-- From here on out, cID and dvContract exist,
-- until the close paren of the enclosing do
if (show dv == "PartyC") then do
let a = dvContract.abc
-- From here on out, a exists
b = dvContract.def
-- From here on out, b exists
-- a and b no longer exist after this line
else if (show dv == "PartyD") then do
let c = devSecCon.ghi
-- From here on out, c exists
d = devSecCon.jkl
-- From here on out, d exists
-- c and d no longer exist after this line
) -- cID and dvContract no longer exist
-- none of cID, dvContract, a, b, c, or d exist here
create ContractA with a= a; b= b; c= c; d= d; ..
Hope this helps.
Hi @Gary_Verhaegen Thanks for your help. But if I want to access a,b,c,d outside the forA loop for contract creation. How can do it? How can I access those a,b,c,d values outside which are assigned inside forA and if-else?
You cannot access a binding outside of its defined scope. What you can do is return the value and give it a new binding. For example:
module Main where
import Daml.Script
even : Int -> Bool
even n = n % 2 == 0
odd : Int -> Bool
odd = not . even
setup : Script ()
setup = script do
let ls = [1, 2, 3, 4, 5]
let (evens, odds) = let evens = filter even ls
in let odds = filter odd ls
in (evens, odds)
debug (evens, odds)
This may be confusing to humans, as the names odds and evens are reused. It is not confusing to Daml because they are used in separate, non-overlapping scopes. In other words, the above is exactly equivalent to:
module Main where
import Daml.Script
even : Int -> Bool
even n = n / 2 == 0
odd : Int -> Bool
odd = not . even
setup : Script ()
setup = script do
let ls = [1, 2, 3, 4, 5]
let (evens, odds) = let a = filter even ls
in let b = filter odd ls
in (a, b)
debug (evens, odds)
In slightly more details:
agets bound to the value[2, 4]bgets bound to the value[1, 3, 5]- The final return value of the
let a = ... in (a, b)line is([2, 4], [1, 3, 5]), and that gets bound to(evens, odds), which means that, within the body of thedo, starting just after thelet (evens, odds) = ...line,evensis bound to the value[2, 4]whileoddsis bound to the value[1, 3, 5].
Now, what does that mean in your case? Quite frankly, I don’t know. The problem you have is that the forA operation will execute the given function for each element, which means that values of a and b, or c and d, will exist for each element in the list. If you start with a list of 5 elements, maybe you will end up with four times a and b and one time c and d. What do you want to return in that case? There isn’t really an immediately obvious definition for
those a,b,c,d values
There is an additional constraint that the Daml language enforces: when you have an if-then-else construct, both branches have to return the same type of value. So in order to return anything from the nested function (starting with \dv -> do), you’ll need to decide on a type both can return. I can’t tell you what that type could be as I don’t know what the types of abc, def, ghi and jkl are. And then you still have to deal with the fact that you’ll end up with a list of those.
Thanks @Gary_Verhaegen . I will try this out. Also, if I have to use string in case statement, how does that work?
So for e.g.:
I have already fetch contract by fetchByKey. devSecCon is the contract object.
case partyToText dv of
"DV1" ->
let a = dvSecCon.abc
"DV2" ->
let b = dvSecCon.def
None -> abort "No valid DV"
Will this code work?
@bernhard Can you also take a look at it?
You can match on strings like that, yes. But your use of case and let won’t work. if... and case... statements in Daml are expressions - they take values. They are not like if... or switch in Java.
Where in Java you’d write
SomeType a = null;
SomeType b = null;
switch (partyToText(dv)) {
case "DV1":
a = dvSecCon.abc;
break;
case "DV2":
b = dvSecCon.def;
break;
default:
throw new RuntimeException("No valid DV")
}
in Daml you’d write
let (a, b) = case partyToText dv of
"DV1" -> (Some dvSecCon.abc, None)
"DV2" -> (None, Some dvSecCon.def)
_ -> error "No valid DV"
Each branch of the case statement needs to return the same type, which in this case is (Optional SomeType, Optional SomeType).
Each branch of the case statement needs to return the same type
I would add that all patterns in a case statement also need to be of the same type. In your example:
case partyToText dv of
"DV1" ->
let a = dvSecCon.abc
"DV2" ->
let b = dvSecCon.def
None -> abort "No valid DV"
"DV1" and "DV2" are of type Text, while None is of type Optional a. All of the patterns must be of the same type, and of the same type as the expression in-between case and of. In this case partyToText returns a Text so you cannot try to match it with None. In other words, the language guarantees you will have a Text value.
What you do probably need is a fallback case for instances where the party is neither "DV1" nor "DV2". Such a fallback case is usually represented by binding the name _, like @bernhard did in his last code snippet.
Thanks @bernhard and @Gary_Verhaegen for your reply.
From my understanding, I modified my case statement snippet to below:
do
(DVData1, DVData2) <- forA parties (\dv ->
do
(cID, dvSecCon) <- fetchByKey @ContractA (dv, id)
case partyToText dv of
"DV1" ->
let
data1 = RD
with
a = devSecCon.abc
b = dvSecCon.def
return (data1)
"DV2" ->
let
data2 = RD
with
a = devSecCon.abc
b = dvSecCon.def
return (data2)
_ -> error "No valid DV"
)
create ContractB with data1= DVData1; data2 = DVData2; ..
I am getting error on DV2 inside case statement. Error is:
parse error (possibly incorrect indentation or mismatched brackets)parser
I have indented the original code properly though here there might be some issues with spaces.
@bernhard and @Gary_Verhaegen can you please help me find out what’s going wrong here?
@Pris17 can you please share the full error message and ideally a standalone example if possible so we can reproduce this?
@cocreature I have pasted full error here. This is what I see when I hover over red line in VSCode. Where can find more details on error?
The easiest way to get something you can copy is probably to run daml build in a terminal and copy it from there. Alternatively, click on View -> Problems via the top menu and that should show you the full error as well.
I also suggest to share the code snippet via a github gist if possible, that way we can sort out the indentation issues fast.
In View → Problems, this is the only error message displayed.
What I’d like to see is the line number which should be displayed there and the line numbers of the original code snippet (or as mentioned above, ideally the full code snippet) so we can see which part of the code is causing the error.
I see one syntax problem in these blocks:
"DV1" ->
let
data1 = RD
with
a = devSecCon.abc
b = dvSecCon.def
return (data1)
Here the value return (data1) is inside the let block. That doesn’t work. Every element in a let block must have the form var = expr.
The thing on the right hand side of the -> must itself be an expression. There are two possibilities:
Using let..in...
"DV1" ->
let
data1 = RD
with
a = devSecCon.abc
b = dvSecCon.def
in return (data1)
or using do
"DV1" -> do
let
data1 = RD
with
a = devSecCon.abc
b = dvSecCon.def
return (data1)
@Pris17 as a general pointer, a lot of the topics discussed here are covered in the introductory documentation to Daml: An introduction to Daml — Daml SDK 1.14.0 documentation
I can highly recommend working your way through that.
Both of above possibilities doesn’t work. I still have indentation issues. After going with 2nd option from your reply, I am now getting below error on the closing bracket of forA loop.
parse error (possibly incorrect indentation or mismatched brackets)parser
I even tried removing do and indenting return. return is not inside let now. Still I face below error
parse error on input ‘return’parser
I can only echo what @cocreature said above: Without complete, reproducible code-samples, it is very difficult to diagnose and help.
I can see two other issues in that latest code snippet. First, the value produced by forA will be a list, so you will not be able to bind it to a pair ((DVData1, DVData2) <- ...). Second, Daml requires that identifiers that start with an uppercase letter be types, while identifiers that start with a lowercase letter be values. Hence, Daml will interpret DVData1 as a type and it is not valid to have a type in that position. You can correct that one by just changing to dvData1 instead.
You have not explained what it is you are actually trying to do, which makes helping you a lot more difficult. From what I can gather so far, the code you’re trying to write will:
- Take a list of parties and a specific identifier
- For each party, try to find a contract with that identifier as (part of) its key
- Return the data from the contract
I don’t know why you want to do that, and there are a few caveats I’ll go into afterwards, but here is a self-contained sample code that does something like that:
module Main where
import Daml.Script
template ContractA
with
sig: Party
id: Text
arg1: Text
arg2: Int
where
signatory sig
key (sig, id) : (Party, Text)
maintainer key._1
data Result = Result with a: Text, b: Int
deriving (Eq, Show)
template ContractB
with
sig: Party
payload: [Result]
where
signatory sig
template DoStuff
with
sig: Party
where
signatory sig
preconsuming choice CreateContractB : ContractId ContractB
with parties: [Party]
id: Text
controller sig
do
-- Note: results is a list
results <- forA parties (\party -> do
(cid, payload) <- fetchByKey @ContractA (party, id)
case partyToText party of
"Alice" -> do
let data1 = Result
with
a = payload.arg1
b = payload.arg2
return data1
"Bob" -> do
let data2 = Result
with
a = payload.arg1
b = payload.arg2
return data2
_ -> error "No valid party")
create ContractB with payload = results, ..
setup : Script ()
setup = script do
alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
carol <- allocatePartyWithHint "Carol" (PartyIdHint "Carol")
-- Fails because no ContractA contract exists yet
submitMustFail alice do
createAndExerciseCmd (DoStuff alice) CreateContractB with parties = [alice, bob], id = "id"
submit alice do
createCmd ContractA with sig = alice, id = "id", arg1 = "some text", arg2 = 42
-- succeeds because there is one contract for Alice and Alice can see it
submit alice do
createAndExerciseCmd (DoStuff alice) CreateContractB with parties = [alice], id = "id"
-- fails because there is no contract for Bob, so the `fetchByKey` command fails
submitMustFail alice do
createAndExerciseCmd (DoStuff alice) CreateContractB with parties = [alice, bob], id = "id"
return ()
This is all syntactically correct, and vaguely looks like your code. However, there are a number of issues with this design:
- The
CreateContractBchoice will fail as soon as you cannot fetch a contract in the given list of parties, either because you cannot see it (see Daml privacy rules and specifically rules for fetching a contract by key). - The choice will also fail as soon as the list passed in as a parameter contains anything else than
aliceorbob, because of the explicitcaseon party name (and the expliciterrorrather than just ignoring non-matching names). It will also fail on most other ledgers than sandbox, because in general party names are not strictly equal to the given hint. - You’d be much better off filtering the list of parties before sending it to the choice.
- This code is, ultimately, doing the same thing for both
"Bob"and"Alice"cases, so it could be rewritten without a case at all.
Without knowing what you are trying to achieve, it’s hard to tell you where to go from here, but hopefully this will help a bit.
TC.daml (1.2 KB)
I have attached the code here. There is parse error on line number 38.
@bernhard @Gary_Verhaegen Can you please quickly take a look at the code snippet and help me resolve the issue. Thanks
You didn’t follow either of my suggestions. The blocks after -> contain neither do nor in.
Using
let..in..."DV1" -> let data1 = RD with a = devSecCon.abc b = dvSecCon.def in return (data1)or using
do"DV1" -> do let data1 = RD with a = devSecCon.abc b = dvSecCon.def return (data1)
There are a couple of other indentation issues. The closing bracket in line 46 needs to be indented more, and the create in line 48, too. Then you have a missing semicolon in line 48.
TC.daml (1.3 KB)
Attached is updated file. Neither of both the above mentioned works. Using let …in as well as do…They give errors on line number 47. Attached code has do inside the case. Still it throws error on line 47.
Error is:
parse error (possibly incorrect indentation or mismatched brackets)parser
Here is the module free of syntax errors:
module TC where
data SRData = SRData
with
a: Text
b: Text
deriving (Eq, Show)
template MRCreate
with
partyA : Party
id : Text
where
signatory partyA
controller partyA can
nonconsuming TestMR : ContractId MR
with
otherparties : [Party]
do
mcID <- lookupByKey @MR (partyA, id)
case (mcID) of
Some mcId -> return mcId
None -> do
[dv1data, dv2data] <- forA otherparties (\dv -> do
(cID, dvCon) <- fetchByKey @ContractB (dv, id)
case partyToText dv of
"DV1" -> do
let srdata1 = SRData
with a = dvCon.abc
b = dvCon.def
return (srdata1)
"DV2" -> do
let srdata2 = SRData
with a = dvCon.abc
b = dvCon.def
return (srdata2)
_ -> error "No valid DV")
return [dv1data, dv2data]
create MR with sRData1= dv1data; sRData2= dv2data; ..
template MR
with
partyA: Party
sRData1: SRData
sRData2 : SRData
where
signatory partyA
key (partyA, sRData1.a): (Party, Text)
maintainer key._1
It is still failing on the absence of a definition for ContractB, though.
As a side note, your code uses tabs instead of spaces. I highly recommend against doing that in indentation sensitive languages like python or Daml. It makes it super easy to end up with situations that look like they are indented properly but are not.
Thanks @Gary_Verhaegen …It is working as expected now. Thanks all for your help.