Functions in data structure

Hi,

I want to put a function in a structure but it fails in the deriving (Eq, Show) as it does not know how to deal with showing or equaling that. I thought those functions would be first class objects. How can I do this?

thanks

3 Likes

Functions are first class objects, and can be put into data types, but there is no automatic derivation of Eq and Show for them, nor are they serializable. The last part is probably the crucial one. You can’t put functions inside templates. Ie you can’t do

template Foo
  with
    fn : Int -> Int
    ...

You can do

data Foo = Foo with
  fn : Int -> Int

But you can’t then put a Foo in a template, or use automatic derivation of Eq or Show.

In most cases I’ve seen, what you actually need is to store a parametrisation of a function. Ie you have a function fn : a -> b -> c and want to store a partial application fn x : b -> c or some such. In that case, it’s best to just store x on the template.

3 Likes

Thanks @bernhard. It indeed shows my issue and that in fact it is not treated the same than other types since I cannot put a function in template, which is what I need.

Why this strong restriction? Can that be fixed?

1 Like

Not easily. Storing functions really means storing closures, which in turn means dragging the environment in which a function was defined along with it. It’s possible, of course, but the complexity and safety tradeoff doesn’t make it worthwhile in my opinion. Also think about what you’d get via the Ledger API.

I’d really be interested in your use-case.

2 Likes

Please have a look at the highly related post Functions - Can we pass them as arguments in a choice? as well.

1 Like

@bernhard my use is really basic in that I need to apply multiple rules and the best outcome.

I could create templates and artificially use this as a set of closures. It would work but is quite heavy … plus create issues of visibility …

1 Like

@Jean_Safar I think you can do something along the lines of Functions - Can we pass them as arguments in a choice? then.

Suppose you have top-level (ie defined at module level) functions

f : Int -> Text -> Bool
g : Decimal -> Text -> Bool

You can define type

data MyFunction = F Int | G Decimal
    deriving (Eq, Show)

and function

exec : MyFunction -> Text -> Bool
exec fn = case fn of
  F i -> f i
  G d -> g d

Now MyFunction represents a function Text -> Bool and you can put MyFunction in your template. When you want to call the function fn : MyFunction with argument t : Text, you call exec fn t.

Does this fit what you need to do? If not, where does it fall short?

2 Likes

Thanks @bernhard, I can do that switch! It should really be done by daml natively though

This solution is still very static since the summation type needs to be known by the contract when it’s coded but that data type is dynamic by nature and is only known at the time of creation of the contract :frowning: by the dapp using that contract!

1 Like

Do you have examples of storage systems where this is solved in a good way? MongoDB used to allow you to store functions (called Javascript code with scope), but they deprecated the feature and I think for good reason. As far as I understand, they didn’t actually store the closure, but a sort of closure with holes that were filled when loading the function. This is demonstrated well in this StackOverflow answer.

Apache Spark, I believe is able to serialize functions with real closures, but this leads to exactly the problem that the enclosing environment keeps being serialized and shipped around. See this StackOverflow answer for the types of problems that causes on UX, not to even get started on the performance implications. Apache Spark is also not a storage system so they don’t have to worry about having to serialising that sort of thing over an externally consumable API.

Some other distributed systems frameworks/languages also allow function serialisation. Erlang, I believe does. But again the top search results lead me to StackOverflow articles which simply say “Just Don’t Do It”.

This is a different problem. DAML was designed around the principle that when a template is defined, all possible outcomes of choices should be predictable. This is a safety measure. Famous attacks like the DAO exploit worked exactly by implementing interfaces in unexpected ways. What that means in practice, is that in DAML you need to know all possible functions at the time of writing the template. As you say, the sum type is static.

We are aware that this is a pretty limiting restriction on DAML. It makes it impossible to dynamically extend functionality without upgrading templates. But support for unbound type-parameters on the Ledger, for existential types, or extensible sum types would open DAML up to exactly the type of exploit described above. I’ve got a few thoughts on how to balance these concerns, which I recently discussed with @Martin_Huschenbett. In short, I think we need to some up with some sort of “permissioned interface”, a way for stakeholders to dynamically extend the list of allowed functions without having to upgrade the contract the functions are to be used in.

2 Likes

s/impossible/cumbersome/

module Main where

import Daml.Script

data Formula =
    Arg1
  | Arg2
  | Plus with arg1: Formula, arg2: Formula
  | Minus with arg1: Formula, arg2: Formula
  | Times with arg1: Formula, arg2: Formula
  deriving (Eq, Show)

eval: Formula -> Int -> Int -> Int
eval f a b = case f of
  Arg1 -> a
  Arg2 -> b
  Plus f1 f2 -> (eval f1 a b) + (eval f2 a b)
  Minus f1 f2 -> (eval f1 a b) - (eval f2 a b)
  Times f1 f2 -> (eval f1 a b) * (eval f2 a b)

template Calculator
  with
    owner: Party
    formula: Formula
    obs: Party
  where
    signatory owner
    observer obs
    nonconsuming choice Eval: Int
      with a : Int, b: Int
      controller obs
      do
        return $ eval formula a b

setup : Script ()
setup = script do
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")

  c1 <- submit bob do createCmd Calculator with owner = bob, obs = alice, formula = (Plus Arg1 Arg2)
  c2 <- submit bob do createCmd Calculator with owner = bob, obs = alice, formula = (Minus (Times Arg1 Arg1) (Times Arg2 Arg2))

  res1 <- submit alice do exerciseCmd c1 (Eval 3 4)
  res2 <- submit alice do exerciseCmd c2 (Eval 3 4)

  assert (res1 == 7)
  assert (res2 == -7)

  return ()
1 Like

Thanks @bernhard, but you do this from a script!

1 Like

Sure, DAML is turing complete so I could store arbitrary functions as text and interpret them in DAML. But that for me doesn’t extend functionality after the fact. It’s using an escape hatch to encode arbitrary functionality from the get-go.

1 Like

I don’t understand what you mean by that. DAML Script doesn’t serialize functions. DAML Script is translated into Ledger API commands client-side.

1 Like

Yes, I want this to be in a compiled template on all nodes. I will try and connect on slack… I might be thinking this wrong! ;-))

1 Like