Generic data in templates - how?

I know currently template DAML does not have support for generics. Still, I was wondering why is that and what are the options if someone wants to achieve something similar?

Assume I want something like the following:

template DataHolder
  with
    owner : Party
    value: ???
  where
    signatory owner

The options I can think of for the ??? part:

  • expose the type Any which is now internal. That way I think one could achieve some sort of generic behaviour (maybe with some typeclasses involved)
  • use Text and store arbitrary values, e.g. JSON. Maybe create a newtype out of it and develop the facilities to deal with that type. (E.g.: easy, encapsulated way to work with the JSON structure)
  • use sum types. This approach is limited compared to the others in the sense that it does not allow arbitrary runtime types. But it does give more type safety

Any thoughts?

4 Likes

DAML used to support generic templates as an experimental feature, but they conflicted badly with other critical features DAML needed to support, so they were eventually dropped. In the project I’m working on, we made extensive use of generic templates when they were available.

We have been able to replace their use with typeclass constraints and functional dependencies, the result does involve more boilerplate, as we have to replace some of the machinery that was being generated by the DAML complier

module GenericTemplates where

import DA.Record
import DA.Functor

class Template t => Perform t a | t -> a where
  perform : ContractId t -> a -> Update a
  
generic : Perform t a => Party -> ContractId t -> a -> Scenario a
generic p fid a = do
  submit p $ perform fid a

genericFetch : (Template t, HasField "field" t a) => ContractId t -> Update a
genericFetch fid = (.field) <$> fetch fid

template Foo
  with
    party : Party
    field : Int
  where
    signatory party

    controller party can
      PerformFoo : Int
        with
          param : Int
        do
          pure field
  
instance Perform Foo Int where
  perform fid param = exercise fid PerformFoo with param

template Bar
  with
    pty : Party
    field : Text
  where
    signatory pty

    controller pty can
      PerformBar : Text
        with
          param : Text
        do
          pure field
  
instance Perform Bar Text where
  perform fid param = exercise fid PerformBar with param 

test : Scenario Int
test = scenario do
  p <- getParty "party"

  fid <- submit p $ create Bar with pty = p; field = "Text"
  b <- submit p $ genericFetch fid
  generic p fid b

  fid <- submit p $ create Foo with party = p; field = 10
  f <- submit p $ genericFetch fid
  generic p fid f

Probably best to avoid something so primitive as the typeclass above, just wrapping a single field access or exercise is generally going to be a code smell. However, you can represent business contract protocols this way — generic business workflows that can be parametized by the typeclass constraints representing various “roles” different templates can play within them. Each template then declares an instance of that role typeclass to specify exactly how it participates. So for instance you might have:

class Template t => Fungible t where
  quantity : t -> Int
  split : Int -> ContractId t -> Update (t, t)
  merge : ContractId t -> ContractId t -> Update t

Different asset classes would provide their own instance of this typeclass, and therefore be able to participate in a business workflow (assuming the existence of a Transferable typeclass as well:

transferQuantity : (Transferable t, Fungible t) => Int -> ContractId t -> Party -> Update (ContractId t, ContractId t)
3 Likes

You can do quite a lot of magic with type classes and that was indeed how Generic Templates were implemented in the background when they were available. DAML-LF’s type system is much simpler though so all that magic gets compiled away and is thus not available when including dependencies as data-dependencies, which is the only way they can be included across SDK. So what you’ll have available is the fully applied templates, and instances defined, but not the type classes, or the relationship between instances of a type class. So it is not possible to write a library of generic templates like Perform above, that is usable across SDK versions, and that is exactly why we put the Generic Template feature on ice again.

To do it properly, we need to extend DAML-LF’s type system, but that bears its own risks. If we make it too flexible, we lose safety. So there is a design challenge there for next gen Generic Templates.

3 Likes

Assume we have the following:

data Value = A | B deriving (Eq, Show)

template ValueHolder
  with
    party : Party
    value : Value
  where
    signatory party

The goal
Have some sort of dynamicity, meaning if values of type C come in one can add those without recompilation and redeploy.

With current approach this is obviously not doable. Is it possible to redesign this code in any way to achieve this goal?

@Andrae mentioned the use of typeclasses. Can this work? I’m not sure how…

Would it make sense to make the type Any publicly available? Then one could make the field value: Any.

Please correct me if I’m wrong, but I think that there doesn’t exist a safe language that has this capability, and if a language does have this capability it isn’t really safe.

One could encode an entire language via expressions inside of data Value, the way @cocreature does here Functions - Can we pass them as arguments in a choice?.

I interpret @Andrae’s example to demonstrate that the parametericity you need probably depends on what you intend to do with your templates. So you could have generic choices. But if you’re looking for a mechanism to avoid repeating share state can you have a generic data type that has a specific instance on each template?

Barring a good example, I don’t think that exposing the Any type is a good solution. Who would want to sign such a contract? I’m not certain that it provides the mechanism that you’re looking for, (something akin to Data.Dynamic?) Even if you were to have that you’ll still have to compile the lookup.

1 Like

I think what @Tamas_Kalczai is after is an existential type. We don’t support those. If we did, you’d be able to express wildcard agreements like “I agree to exercise any choice” so there’s definitely a tradeoff with security.

3 Likes

Thanks for this response @bernhard . I was about to post a new question that is almost literally the Expr example found here:

https://wiki.haskell.org/Existential_type#Cases_that_really_require_existentials

Based on your answer this isn’t going to work in Daml.

(I’m still pretty happy that I was able to figure out how to do this in Haskell.)

1 Like