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
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
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.
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?
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.
Please have a look at the highly related post Functions - Can we pass them as arguments in a choice? as well.
@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 …
@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?
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 by the dapp using that contract!
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.
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 ()
Thanks @bernhard, but you do this from a script!
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.
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.
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! ;-))