If-Then-Else statements inside forA loop in DAML

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?

1 Like

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

  1. Use pattern matching instead of if-else statements
  2. 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:

  1. Use partyFromText "PartyC", invoking fromSomeNote on that to get a Party-typed value for "PartyC". Bind it to local variable partyC.
  2. (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 in otherParties, doing this will preserve that intention, but maybe you didn’t really mean that.
  3. Use fetchByKey just as you currently do to get the dvContract.
  4. Bind your abc, def fields from that contract to a and b, using let just as you do now.
  5. Repeat steps 1-4 for every other party for which you need to fetch a contract and extract values to local variables.
  6. At the end of the do, your create 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:

  1. there is no forA
  2. there is no if (though we do use elem and assert, if that is your intention)
  3. there is no nested do
1 Like

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

1 Like

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?

1 Like

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.

2 Likes

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?

1 Like

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 the do, starting just after the let (evens, odds) = ... line, evens is bound to the value [2, 4] while odds 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.

2 Likes

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.

1 Like

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)
1 Like

@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.

2 Likes