Functions - Can we pass them as arguments in a choice?

Template fields as well as choice arguments need to be serializable. Functions are not serializable in DAML so you cannot use them in those contexts. Depending on your application there are however ways around this in some contexts:

Often your application does not need the ability to pass in arbitrary functions but it can instead get away with only a finite number of functions or functions built upon a limited set of primitives. Let’s go through a simple example:

template Modifier
  with
    p : Party
    f : Int -> Int
  where
    signatory p
    nonconsuming choice ModifyValue : Int
      with
        value : Int
      controller p
      do pure (f value)

We have a template with a field that stores a function and then a choice that allows us to use this function. Note that this is example is very simplified and probably not something you would ever want to use in that form.

Now, this will not compile since as mentioned above functions are not serializable. But does our application really need arbitrary functions in here? Let’s say that actually we only need to differentiate between incrementing the value by one or decrementing it by one.

data ModifierFunction = IncByOne | DecByOne deriving (Eq, Show)

interpret : ModifierFunction -> (Int -> Int)
interpret IncByOne i = i + 1
interpret DecByOne i = i - 1

Here we have defined a type that can represent these two functions (and only those) and a function to go from this datatype back to a function. ModifierFunction is serializable! This allows us to modify our template as follows:

template Modifier
  with
    p : Party
    f : ModifierFunction
  where
    signatory p
    nonconsuming choice ModifyValue : Int
      with
        value : Int
      controller p
      do pure (interpret f value)

This one will compile and work as expected!
Now supporting only two functions might seem very limiting but this pattern can be extended. An obvious extension is to add a parameter to specify how much we want to add to the value. We can specify negative values so no need to differentiate between incremnting and decrementing.

data ModifierFunction = Add Int deriving (Eq, Show)

interpret : ModifierFunction -> Int -> Int
interpret (Add n) i = i + n

But if necessary, we do not need to stop here, we can add multiplication and function composition and then we can already express something like the function \x -> (x + 23) * 42 using Compose (Mul 42, Add 23).

data ModifierFunction = Add Int | Mul Int | Compose (ModifierFunction, ModifierFunction) deriving (Eq, Show)

interpret : ModifierFunction -> Int -> Int
interpret (Add n) i = i + n
interpret (Mul n) i = i * n
interpret (Compose (f, g)) i = interpret f (interpret g i)

Conclusion

You probably already notice that while this can be taken much further it also gets increasingly unwieldy and becomes hard to use. So in general, I would suggest that you first think about whether you really need functions in your templates or choices or if there is another solution, e.g., maybe instead of trying to store the function on the ledger and perform the computation there you can perform the computation in the client. However, if that is not an option or you really just need a small number of functions like in the first example with Inc and Dec then this gives you a way to implement this.

5 Likes