A “normal” Daml function’s type has the form
X -> Y -> Z
Y are argument types, and
Z is the result type, all separated by
But there are many definitions in Daml that have a different symbol,
=>. Like so:
C => Z
Z might have some
-> of its own. It looks sort of like a function type, but most of the time the call of the function contains no reference whatsoever to
So what is this thing? We call the part before the
=> a constraint, or typeclass constraint in common parlance.
One of the interesting properties of this whole constraint system is that aside from its appearance in type errors, you don’t really have to understand it until you’re ready to understand it. So if you’re a new Daml user, maybe don’t worry too much about anything in this article. If you want to know, though, here it is.
A typical generic function—one that can work with many types, not just one set of argument and result types—might be
map, whose signature is
map : (a -> b) -> [a] -> [b]
The way to read this signature is “I can substitute any type I want for
b, as long as I do it consistently.” So all of these are valid uses of
(Int -> Int) -> [Int] -> [Int] (Int -> Text) -> [Int] -> [Text] (ContractId IouTransfer -> ContractId Iou) -> [ContractId IouTransfer] -> [ContractId Iou]
Moreover, the function will behave consistently, no matter what types you substitute; it will not do something different just because you’ve set
b = Text rather than
b = Int.
By contrast, this is an invalid expansion, because it does not set
b consistently across the type signature:
(Int -> Text) -> [Int] -> [ContractId Iou]
That said, you can pick any types you want for
However, consider what that would mean for the
create function from the Daml standard library, if it was typed like this:
create : t -> Update (ContractId t)
This works fine for templates like
IouTransfer. However, according to our rule for setting type variables, it also allows something like
Int -> Update (ContractId Int) Text -> Update (ContractId Text)
However, it doesn’t make sense to create an
Text; they aren’t templates, so you can’t have contracts on the ledger with them. They don’t even have signatories. We wouldn’t even know where to start creating an
We can’t take out the type variable, because
create must work for many types, all template types in fact. We just don’t want it to work for all types. So we want some way to constrain the
t type variable to allow template types like
Iou, but disallow types in general.
create has the signature
create : (HasCreate t) => t -> Update (ContractId t)
If you want to understand “by example”, as it were, the approach is always the same: substitute concrete types for the type variables and see if it makes sense.
What if we wrote some
template Iou and tried to
create it? We set
t = Iou to match the argument type we pass to
create, and the signature becomes
(HasCreate Iou) => Iou -> Update (ContractId Iou)
Remembering that our call never mentions what’s before the
=>, this function takes an
Iou payload and returns a ledger action that yields a
By contrast, consider what happens if we set
t = Int:
(HasCreate Int) => Int -> Update (ContractId Int)
This is well-kinded in the language of types, as it were—it correctly defines a type—but creating an
Int contract doesn’t make sense.
Int wasn’t defined with
How do we make it so that you can set
t = Iou, or any other template type for that matter, but reject
t = Int as it is not something you can
create? The answer is in the constraint.
In the first case, we have
HasCreate Iou. In the second we have
HasCreate Int. If we try the latter, we get
daml> create 42 <string>:1:1: error: • No instance for (HasCreate Int) arising from a use of ‘create’
HasCreate exists, and the second one doesn’t. That’s how we figure out there’s an error, and report it.
First and foremost, typeclasses classify types. For every type
HasCreate t exists, or it does not; there is no in-between. Moreover, this is a fact about the compile-time type, not about “objects”, a runtime concept; we answer this question while compiling code, and it is never an ambiguous answer.
HasCreate Iou exists, and
HasCreate Int does not. So the constraint is solved in the first case, and not solved in the second. That’s because, in the
HasCreate typeclass, the typeclass instance
HasCreate Iou exists, but there is no instance for
So how can we read
create : (HasCreate t) => t -> Update (ContractId t)? “
create has a function type that takes a
t as an argument and returns a ledger action that yields a
ContractId t, where type
t ‘has a create’.”
Let’s consider a longer signature. When you break these down into their parts, they’re not as scary as they look.
createAndExercise : (HasCreate t, HasExercise t c r) => t -> c -> Update r
First, a compound constraint
(a, b, c, …) is exactly the sum of its parts. To understand the constraint
(HasCreate t, HasExercise t c r), you only have to understand what
HasCreate t and
HasExercise t c r mean separately.
Given that, let’s read the type. “Given a
t can be created, and exercising choice
c on contracts of type
t will return
r, return a ledger action that yields
Two wrinkles remain here. First, I’ve said that typeclasses classify types, and that remains true even when a typeclass has three type parameters, like
HasExercise t c r. The difference is that
HasCreate classifies single types: a type is either in the class, or it is not.
HasExercise classifies triples of types: any list of 3 types is either in the class, or it is not.
-- so given the Iou choice choice Iou_Transfer : ContractId IouTransfer -- this is in the class HasExercise t = Iou c = Iou_Transfer r = ContractId IouTransfer -- and substituting these for the createAndExercise type, we get (HasCreate Iou, HasExercise Iou Iou_Transfer (ContractId IouTransfer)) => Iou -> Iou_Transfer -> Update (ContractId IouTransfer) -- since the constraints can be resolved, -- everything before the => then goes away
The second wrinkle is, how can we interpret
HasExercise t c r as “exercising choice
c on contracts of type
t will return
A typeclass is two things put together:
- the types that are part of it, and
- the values listed under its
So we can figure out how to read the constraint by looking at those two things.
First, let’s look at (2), the definition of
HasExercise. We can find it in the Daml docs:
class HasExercise t c r where exercise : ContractId t -> c -> Update r
That’s enough to tell us we’re operating on contracts of template type
t, and that the result is a ledger operation returning
c, which could be anything, looking at this signature? We can take a hint from the documentation for
exercise, “Exercise a choice on the contract with the given contract ID.” Answering the question of “what is
HasExercise”, (2) is just this
exercise method. So understanding what this sole method does is all we need to know in that respect.
To figure out (1), the types that are part of
HasExercise, we could look at some examples. In every example you’ll find, the second argument is some choice on the template of the contract identified by the contract ID.
We can test our assumptions in Daml Studio, though. Try adding to a Daml file alongside
foo = exercise @Int @Int -- red-line error error: • No instance for (HasExercise Int Int r0) arising from a use of ‘exercise’ • In the expression: exercise @Int @Int
We can do this because the first two type arguments are
c, which correspond to what we might hypothesize are the template type and choice type.
However, plug in this instead:
foo = exercise @Iou @Iou_Transfer -- and daml studio will have no errors, and even have the line above foo : ContractId Iou -> Iou_Transfer -> Update (ContractId IouTransfer) -- which shows by absence of the `HasExercise` constraint -- that it was successfully resolved -- We can check our understanding of r, too: foo = exercise @Iou @Iou_Transfer @(ContractId IouTransfer) -- which shows exactly the same inferred foo type as above. -- Put in something wrong like @Int and we get error: • Couldn't match type ‘ContractId IouTransfer’ with ‘Int’ arising from a functional dependency between: constraint ‘HasExercise Iou Iou_Transfer Int’ arising from a use of ‘exercise’ instance ‘HasExercise Iou Iou_Transfer (ContractId IouTransfer)’
These are all various ways the Daml compiler has of explaining
- There are no
HasExerciseinstances where (t =
Int, c =
- there is an instance where t =
Iou, c =
Iou_Transfer, and r =
- there are no instances for any other r, such as
Int, where t =
Iouand c =
Since there is no deeper meaning of
HasExercise to plumb beyond its types and its methods, we’ve done more than enough to safely interpret
HasExercise t c r as “exercising choice
c on contracts of type
t will return
Sometimes we like to bundle constraints under names in the standard library. You can do this too:
type Template t = (HasSignatory t, HasObserver t, HasEnsure t, HasAgreement t, HasCreate t, HasFetch t, HasArchive t, HasTemplateTypeRep t, HasToAnyTemplate t, HasFromAnyTemplate t)
Again, I discourage reading too much into the name
Template here. This constraint means exactly the same as the 10 listed constraints to the right of the
=, no more and no less. Any type that satisfies all 10 constraints will also satisfy
Template t, and vice versa. So when such an alias occurs in a type you are trying to read, you need only read the component parts to understand what the whole constraint means.
With the above ideas in our toolkit, we can read a signature like
queryContractKey from the
(Template a, -- Given 'a', a template type, HasKey a k, -- where 'a's key type is k, Eq k, -- and 'k's can be compared for equality, ActionTriggerAny m, -- and 'm' is an action type for a trigger’s initialize, updateState, or rule, Functor m) -- which can also be mapped from one result type to another: => k -- a function that takes a k -> m (Optional (ContractId a, a)) -- and returns either a pair of an 'a' contract ID and payload or nothing, -- resulting from an m action
I’ll leave determining how I translated these as an exercise for you. The understanding tools I’ve discussed in this article are your guide to making these same determinations.
Some key elements to keep in mind:
- To make an abstract type concrete, substitute concrete types for the type variables and see if your substitution makes sense.
- If a constraint can be satisfied by the Daml compiler, the chosen types are in the class. If it cannot, the types are not in the class.
- A compound constraint
(a, b, c)is exactly the sum of its elements, no more, no less.
- A typeclass consists only of the types that are part of it, and the values listed under its definition.
- You can always ask Daml Studio whether a set of types is in a typeclass.
This article was tested with Daml SDK 2.0.0. It arose out of this discussion of documenting design with typeclasses; thanks to @Leonid_Rozenberg and @asarpeshkar for the inspiration.
I welcome suggestions to improve this tutorial’s clarity, but would rather restrain its scope as-is and leave the bigger questions to other documents. If you find it valuable as a Daml developer, please say so, and we may invest time into documenting more typeclass-related topics.