Hi @prajwalhegde3,
Daml works with a concept of “type classes”. @stephen has written a much more complete tutorial about type classes here. You can also consult the Haskell tutorial on type classes, as type classes in Daml are very similar to their Haskell equivalent.
Assuming you’re looking for a more superficial explanation, here’s my attempt at a summary.
A “class” is a set of types that share some property. In Daml, classes can be defined independently of the types that belong to them. So the pieces are:
- A
class
definition.
- Any number of
data
type definitions.
- For each type that belongs to the class, an explicit definition of its corresponding behaviour.
As a concrete example, let’s think about how we could define a class for “things that can be compared for equality”, and call it Equality
. The class definition could be:
class Equality a where
isEqual : a -> a -> Bool
I’m not going to delve deep into the syntax here, but here’s roughly how to read this:
- The name of the class is
Equality
, and it is defined on a single type at a time, here left as a parameter a
.
- For a type to belong to this class, it needs to give an implementation of
isEqual
, which is a function that takes two a
arguments and returns a boolean. This should return True when the two values are “equal” and False when they aren’t (this is not expressed in this code snippet and should instead be in documentation).
At this point there is no type anywhere in the world that belongs to Equality
, as we haven’t defined any isEqual
implementation. Also note that, implicit in our definition of isEqual
, this can only ever compare two things of the same type a
. If we wanted to compare different types (which arguably is a strange concept for equality, but bear with me), we would have to write a 2-type class:
class EqualityAcrossTypes a b where
isEqualAcrossTypes : a -> b -> Bool
Now let’s define a data type:
data Person = Person { name: Text, address: Text, ssn: Text }
What does it mean for two persons to be the same? Well, we could decide that, for the purposes of our program, only equality of ssn
is necessary, which we would write:
instance Equality Person where
isEqual p1 p2 = p1.ssn == p2.ssn
Now, in most cases, in programming, we’ll want to consider that two records are equal if all of their fields are equal (recursively). This is the meaning of the built-in Eq
class. So the “valid” implementation of Eq
for our Person
data type here would be:
instance Eq Person where
(==) p1 p2 = p1.name == p2.name && p1.address == p2.address && p1.ssn == p2.ssn
Eq
is only special in that it is defined for primitive types. Here, for example, we build the (==)
implementation for Person
based on the (==)
implementation for Text
, which happens to be provided by the base Daml language.
Writing the Eq
instance declaration for Person
is boring. It’s also a bit error-prone: I could mistype one of these p1
for p2
, or forget to add a clause to the definition if I ever add a field to Person
. Therefore, to reduce tedium and error rates, the language supports a notion of automatically deriving simple, mechanical instance declarations directly from the data type definition itself.
This is what happens when you say deriving (Eq)
. After compilation, there is no difference between:
data Person = Person { name: Text, address: Text, ssn: Text }
deriving (Eq)
and
data Person = Person { name: Text, address: Text, ssn: Text }
instance Eq Person where
(==) p1 p2 = p1.name == p2.name && p1.address == p2.address && p1.ssn == p2.ssn
but the former is easier to read, less error-prone, and more robust against changes in the future.