Non aborting forA loop

I want to continue the execution of forA loop even after one of my case conditions is not satisfied. Below is my code snippet

              dvdata <- forA otherparties (\dv -> do
                cID <- lookupByKey @ContractA (dv, id)
                case cID of
                  Some cId -> do
                    dvCon <- fetch cId                   
                    case partyToText dv of
                      "DV1" -> do
                        let sRDV1 = SRData 
                              with a = dvCon.abc
                                       b = dvCon.def
                        return (sRDV1)
                      "DV2" -> do
                        let sRDV2 = SRData 
                              with a = dvCon.abc
                                       b = dvCon.def
                        return (sRDV2)
                      _ -> error "No valid DV"
                  None -> return ())              --Here I want to continue the forA loop execution with next element

How do I achieve this?

What do you want to be returned for the case that currently calls error?

None -> return ())              --Here I want to continue the forA loop execution with next element

@cocreature Here I want to continue with the next element of the forA loop and return nothing.

First off, and I can’t stress this enough: mentioning explicit party names in code is a very bad code smell in Daml. There are very few cases where that is the right approach.

Ignoring that, as @cocreature said, if you don’t want your code to stop you have to return something. The simplest option is to wrap your return value in an Optional and return None for items you’d like to skip.

But first, let’s place your original snippet in a context where it actually compiles:

module Main where

import Daml.Script

template ContractA
  with
    sig: Party
    id: Text
    abc: Text
    obs: Party
  where
    signatory sig
    observer obs
    key (sig, id) : (Party, Text)
    maintainer key._1

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig

data SRData = SRData with a: Text
  deriving Show

setup : Script ()
setup = script do
  party1 <- allocatePartyWithHint "DV1" (PartyIdHint "DV1")
  party2 <- allocatePartyWithHint "DV2" (PartyIdHint "DV2")
  mainParty <- allocatePartyWithHint "mainParty" (PartyIdHint "mainParty")

  submit party1 do
    createCmd ContractA with sig = party1, obs = mainParty, id = "keyData", abc = "some data"
  submit party2 do
    createCmd ContractA with sig = party2, obs = mainParty, id = "keyData", abc = "some other data"
  collect <- submitMulti [mainParty, party1, party2] [] do
    createCmd DoCollection with main = mainParty, others = [party1, party2]

  submit mainParty do
    exerciseCmd collect CreateContractB with parties = [party1, party2], id = "keyData"
  return ()

template DoCollection
  with
    main: Party
    others: [Party]
  where
    signatory main :: others
    nonconsuming choice CreateContractB : ()
      with parties: [Party]
           id: Text
      controller main
      do
        dvdata <- forA parties (\dv -> do
          cID <- lookupByKey @ContractA (dv, id)
          case cID of
            Some cId -> do
              dvCon <- fetch cId                   
              case partyToText dv of
                "DV1" -> do
                  let sRDV1 = SRData with a = dvCon.abc
                  return (sRDV1)
                "DV2" -> do
                  let sRDV2 = SRData with a = dvCon.abc
                  return (sRDV2)
                _ -> error "No valid DV"
            -- Note: can't return `()` here because that's the
            -- wrong type
            None -> error "original code errors here")
        debug dvdata

This prints:

[SRData {a = "some data"},SRData {a = "some other data"}]

And the (inferred) type of dvdata is [SRData].

I’ve removed the def part out of pure laziness; the main change is that the None case cannot return (), because there is no type that includes both () and the return values of the Some branch (SRData).

This is, in fact, the same problem you have with the _ case.

Wrapping your return value in an Optional solves both:

      do
        dvdata <- forA parties (\dv -> do
          cID <- lookupByKey @ContractA (dv, id)
          case cID of
            Some cId -> do
              dvCon <- fetch cId                   
              case partyToText dv of
                "DV1" -> do
                  let sRDV1 = SRData with a = dvCon.abc
                  return (Some sRDV1)
                "DV2" -> do
                  let sRDV2 = SRData with a = dvCon.abc
                  return (Some sRDV2)
                _ -> return None
            None -> return None)
        debug dvdata

which prints

[Some (SRData {a = "some data"}),Some (SRData {a = "some other data"})]

where the inferred type of dvdata is now [Optional SRData].

So what actually happens if we’re missing some data? You can try it; for example, removing the creation of party1's contract, we now print:

[None,Some (SRData {a = "some other data"})]

Similarly, adding a third party and creating a contract for them would yield:

[Some (SRData {a = "some data"}),Some (SRData {a = "some other data"}),None]

And, if we both don’t create the contract for "DV1" and add a third party, we’d print:

[None,Some (SRData {a = "some other data"}),None]

One issue with this is that we don’t know why things fail. In this last list, the first None is because the contract was not found, whereas the second None was because the contract was found but did not have the properties we wanted (wrong party in this case). Rather than returning a Optional, we could choose to return a Either. Whereas Optional a has two constructors Some a and None, where only Some a can have attached data, Either a b has two constructors Left a and Right b, both of which can have attached data (of potentially different types a and b).

Using this, we could return some explanation for the error:

      do
        dvdata <- forA parties (\dv -> do
          cID <- lookupByKey @ContractA (dv, id)
          case cID of
            Some cId -> do
              dvCon <- fetch cId                   
              case partyToText dv of
                "DV1" -> do
                  let sRDV1 = SRData with a = dvCon.abc
                  return (Right sRDV1)
                "DV2" -> do
                  let sRDV2 = SRData with a = dvCon.abc
                  return (Right sRDV2)
                _ -> return (Left "wrong party")
            None -> return (Left "contract not found"))
        debug dvdata

which prints:

[Left "contract not found",Right (SRData {a = "some other data"}),Left "wrong party"]

now of type [Either Text SRData].

Finally, maybe you just don’t care about the errors and you just want a list of the contracts that do match. You could write a filtering function yourself:

filterEither : [Either a b] -> [b]
filterEither [] = []
filterEither (e::es) = case e of
  Left _ -> filterEither es
  Right a -> a :: filterEither es

or, in this case, use the builtin DA.Either.rights. For reference, here is the complete code again, this time using Either as the return type, skipping the contract creation for "DV1", adding a "DV3", and printing both the complete list with failures and the list of only the successes:

module Main where

import Daml.Script
import qualified DA.Either

template ContractA
  with
    sig: Party
    id: Text
    abc: Text
    obs: Party
  where
    signatory sig
    observer obs
    key (sig, id) : (Party, Text)
    maintainer key._1

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig

data SRData = SRData with a: Text
  deriving Show

filterEither : [Either a b] -> [b]
filterEither [] = []
filterEither (e::es) = case e of
  Left _ -> filterEither es
  Right a -> a :: filterEither es

setup : Script ()
setup = script do
  party1 <- allocatePartyWithHint "DV1" (PartyIdHint "DV1")
  party2 <- allocatePartyWithHint "DV2" (PartyIdHint "DV2")
  party3 <- allocatePartyWithHint "DV3" (PartyIdHint "DV3")
  mainParty <- allocatePartyWithHint "mainParty" (PartyIdHint "mainParty")

  --submit party1 do
  --  createCmd ContractA with sig = party1, obs = mainParty, id = "keyData", abc = "some data"
  submit party2 do
    createCmd ContractA with sig = party2, obs = mainParty, id = "keyData", abc = "some other data"
  submit party3 do
    createCmd ContractA with sig = party3, obs = mainParty, id = "keyData", abc = "yet another data"


  let others = [party1, party2, party3]  
  collect <- submitMulti (mainParty :: others) [] do
    createCmd DoCollection with main = mainParty, ..

  submit mainParty do
    exerciseCmd collect CreateContractB with parties = others, id = "keyData"
  return ()

template DoCollection
  with
    main: Party
    others: [Party]
  where
    signatory main :: others
    nonconsuming choice CreateContractB : ()
      with parties: [Party]
           id: Text
      controller main
      do
        dvdata <- forA parties (\dv -> do
          cID <- lookupByKey @ContractA (dv, id)
          case cID of
            Some cId -> do
              dvCon <- fetch cId                   
              case partyToText dv of
                "DV1" -> do
                  let sRDV1 = SRData with a = dvCon.abc
                  return (Right sRDV1)
                "DV2" -> do
                  let sRDV2 = SRData with a = dvCon.abc
                  return (Right sRDV2)
                _ -> return (Left "wrong party")
            None -> return (Left "contract not found"))
        debug dvdata
        debug (filterEither dvdata)
        debug (DA.Either.rights dvdata)
3 Likes

@Gary_Verhaegen Thanks for your reply. The code works as expected. I was trying to extract data from DA.Either.rights dvdata which is the list [SRData {a = "some other data"}] having type SRData. I am unable to fetch the value of a. Can’t we just use again forA to iterate over list and extract the value of a from each SRData object?

1 Like

Figured it out. Ignore my previous reply.

1 Like

Glad you figured it out! For the benefit of future readers who might ask themselves the same question, here is how I would have answered.

You should use forA when you want to execute some sequence of ledger actions over a list. In the code above, we do a lookupByKey operation for each Party in the parties list.

If all you want is to extract data you already have from a list, you can apply a function to every element of the list using map, which is a bit like forA in that it will execute the function on each element of the list, but different in that it will not call out to the ledger.

So if you want to have a [Text] instead of a [SRData] (by extracting the a field, which is of type Text), you can do something like:

map (\srdata -> srdata.a) list_of_srdata

Or, in the case where you have [Either Text SRData], as we had in the last code example, you could go for:

debug (map (.a) $ DA.Either.rights dvdata)

which would print

["some other data"]

where (.a) is a shorthand notation for (\x -> x.a) .

2 Likes