Looping with actions

Guys, I have a rookie Daml question I hope someone can help me with. The functionality I need to implement is as follows. I have a template named Asset with a field “amount : Decimal”. In another template I need to create a choice that takes [ContractId Asset] as argument, loops through the list, fetches each contract and calculates the sum of the values of the amount field. Can anyone provide a quick example? I’ve tried a bunch of ways I could think of, but I can’t make it work. I always run into typecheck problems. E.g. I tried using the following function inside choice body

let fetchAssetAmount aCid = do
            a <- fetch aCid
            return a.amount

The function signature is
Contract Id r → Update b. In other words, it returns an action. I want it to return a Decimal, so I could use it in foldl. I don’t understand why the function returns an action and not the value provided using the “return” keyword.
Since the function does return an action, I tried using it with foldlA, but I run into typecheck error again. Here’s a complete code snippet:

template Asset
    with
        amount : Decimal
        owner : Party
    where
        signatory: owner
template Bla
    with
        owner : Party
    where
        signatory : Party
        nonconsuming choice Create_New_Asset : ContractId Asset
            with
                cids : [ContractId Asset]
            controller owner
            do
                 let fetchAssetAmount aCid = do
                     a <- fetch aCid
                     return a.amount
                 totAssetAmount <- foldl1A fetchAssetAmount assetsCids
                 create Asset with
                     owner
                     amount = totAssetAmount

In the above the compiler complaints about typecheck error within fetchAssetAmount in the statement “totAssetAmount ← foldl1A fetchAssetAmount assetsCids”

1 Like

Let’s start with the fixed version:

module Main where

import DA.Action

type AssetId = ContractId Asset

template Asset
    with
        amount : Decimal
        owner : Party
    where
        signatory owner

template Bla
    with
        owner : Party
    where
        signatory owner
        nonconsuming choice Create_New_Asset : ContractId Asset
            with
                cids : [ContractId Asset]
            controller owner
            do
                 let fetchAssetAmount acc aCid = do
                     a <- fetch aCid
                     return $ a.amount + acc
                 totAssetAmount <- foldlA fetchAssetAmount 0.0 cids
                 create Asset with
                     owner
                     amount = totAssetAmount

A few issues:

  1. You have some small syntax errors, e.g., signatory : owner instead of signatory owner. Just look at what your IDE complains about and they should be easy enough to sort out.
  2. assetCids needs to be cids, otherwise you get an out of scope error.
  3. Now you get to the interesting part: First, foldl1A crashes on empty lists. This is sometimes acceptable if you don’t have a good way to handle an empty list. However a sum has a trivial empty case with 0 so let’s use foldlA and use 0.0 as the base.
  4. Lastly, you are missing the accumulator. your folding function needs to take the accumulator so far (in your example the sum) and combine it with the new element (by adding the two).

A slightly more idiomatic version might look like this:

totAssetAmount <- sum . map (\x -> x.amount) <$>  mapA fetch cids

which doesn’t need any fold and just reuses the existing sum function.

foldl1A expects an a -> a -> m a, but the function you’re passing has the shape ContractId Asset -> Update Decimal. There is no way to assign types to the variables a and m such that fetchAssetAmount will fit.

The hint about the mismatch here is those two arguments. You’re passing a list of arguments to call fetchAssetAmount with, but expecting them to percolate down to a single result. As with most fold functions, the question you’re being asked is “how do I combine these values?”

Another problem is specific to the 1 variants of these folding functions: the two input types a must match the output type a. Since your input type is ContractId Asset and output type is Decimal, there’s no way to make that make sense; what would each step mean, if it discarded the Decimal from the prior step and expected you to figure the result from two ContractId Assets?

You can solve these issues by using foldlA instead.

  1. Our initial value is 33.0, used as the starting point.
  2. Our rule for combining the values is to multiply the new number at each step and add 42.
                 let fetchAssetAmount acc aCid = do
                     a <- fetch aCid
                     return (acc * a.amount + 42.0)
                 totAssetAmount <- foldlA fetchAssetAmount 33.0 cids
1 Like

@Stephen, thank you very much for the explanation. I now see the difference in the output type between foldlA and foldl1A, which wasn’t clear to me previously.
@cocreature, thank you for correcting my errors and for providing an idiomatic example. Much appreciated.