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-else
statements - Use
partyToText
for 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
There are ways to deal with this, but I think forA
should not be used at all in this situation.
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"
, invokingfromSomeNote
on 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
fetchByKey
just as you currently do to get thedvContract
. - Bind your
abc
,def
fields from that contract toa
andb
, usinglet
just 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 ContractA
will 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 useelem
andassert
, 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:
a
gets bound to the value[2, 4]
b
gets 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,evens
is bound to the value[2, 4]
whileodds
is 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)
.
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?