10 Intro to the DAML Standard Library¶
In chapters 3 Data types and 9 Functional Programming 101 you learnt how to define your own data types and functions. But of course you don’t have to implement everything from scratch. DAML comes with the DAML Standard Library which contains types, functions, and typeclasses that cover a large range of use-cases. In this chapter, you’ll get an overview of the essentials, but also learn how to browse and search this library to find functions. Being proficient with the Standard Library will make you considerably more efficient writing DAML code. Specifically, this chapter covers:
- The Prelude
- Important types from the Standard Library, and associated functions and typeclasses
- Typeclasses
- Important typeclasses like
Functor
,Foldable
, andTraversable
- How to search the Standard Library
To go in depth on some of these topics, the literature referenced in The Haskell Connection covers them in much greater detail. The Standard Library typeclasses like Applicative
, Foldable
, Traversable
, Action
(called Monad
in Haskell), and many more, are the bread and butter of Haskell programmers.
Note
There is a project template daml-intro-10
for this chapter, but it only contains a single source file with the code snippets embedded in this section.
The Prelude¶
You’ve already used a lot of functions, types, and typeclasses without importing anything. Functions like create
, exercise
, and (==)
, types like []
, (,)
, Optional
, and typeclasses like Eq
, Show
, and Ord
. These all come from the Prelude. The Prelude is module that gets implicitly imported into every other DAML module and contains both DAML specific machinery as well as the essentials needed to work with the inbuilt types and typeclasses.
Important Types from the Prelude¶
In addition to the Native types, the Prelude defines a number of common types:
Lists¶
You’ve already met lists. Lists have two constructors []
and x :: xs
, the latter of which is “prepend” in the sense that 1 :: [2] == [1, 2]
. In fact [1,2]
is just syntactical sugar for 1 :: 2 :: []
.
Tuples¶
In addition to the 2-tuple you have already seen, the Prelude contains definitions for tuples of size up to 15.
Tuples allow you to store mixed data in an ad-hoc fashion. Common use-cases are return values from functions
consisting of several pieces or passing around data in folds, as you saw in Folds.
An example of a relatively wide Tuple can be found in the test modules of the chapter 8 project.
Test.Intro.Asset.TradeSetup.tradeSetup
returns the allocated parties and active contracts in a long tuple.
Test.Intro.Asset.MultiTrade.testMultiTrade
puts them back into scope using pattern matching.
return (alice, bob, usdbank, eurbank, usdha, usdhb, eurha, eurhb, usdCid, eurCid)
(alice, bob, usdbank, eurbank, usdha, usdhb, eurha, eurhb, usdCid, eurCid) <- tradeSetup
Tuples, like lists have some syntactic magic. Both the types as well as the constructors for tuples are (,,,)
where the number of commas determines the arity of the tuple. Type and data constructor can be applied with values inside the brackets, or outside, and partial application is possible:
t1 : (Int, Text) = (1, "a")
t2 : (,) Int Text = (1, "a")
t3 : (Int, Text) = (1,) "a"
t4 : a -> (a, Text) = (,"a")
Note
While tuples of great lengths are available, it is often advisable to define custom records with named fields for complex structures or long-lived values. Overuse of tuples can harm code readability.
Optional¶
The Optional
type represents a value that may be missing. It’s the closest thing DAML has to a “nullable” value. Optional
has two constructors: Some
, which takes a value, and None
, which doesn’t take a value. In many languages one would write code like this:
lookupResult = lookupByKey(k);
if( lookupResult == null) {
// Do something
} else {
// Do something else
}
In DAML the same thing would be expressed as
lookupResult <- lookupByKey @T k
case lookupResult of
None -> do -- Do Something
return ()
Some cid -> do -- Do Something
return ()
Either¶
Either
is used in cases where a value should store one of two types. It has two constructors, Left
and Right
, each of which take a value of one or the other of the two types. One typical use-case of Either
is as an extended Optional
where Right
takes the role of Some
and Left
the role of None
, but with the ability to store an error value. Either Text
, for example behaves just like Optional
, except that values with constructor Left
have a text associated to them.
Note
As with tuples, it’s easy to overuse Either
and harm readability. Consider writing your own more explicit type instead. For example if you were returning South a
vs North b
using your own type over Either
would make your code clearer.
Typeclasses¶
You’ve seen typeclasses in use all the way from 3 Data types. It’s now time to look under the hood.
Typeclasses are declared using the class
keyword:
class HasQuantity a q where
getQuantity : a -> q
setQuantity : q -> a -> a
This is akin to an interface declaration of an interface with a getter and setter for a quantity. To implement this interface, you need to define instances of this typeclass:
data Foo = Foo with
amount : Decimal
instance HasQuantity Foo Decimal where
getQuantity foo = foo.amount
setQuantity amount foo = foo with amount
Typeclasses can have constraints like functions. For example: class Eq a => Ord a
means “everything that is orderable can also be compared for equality”. And that’s almost all there’s to it.
Important Typeclasses from the Prelude¶
Eq¶
The Eq
typeclass allows values of a type to be compared for (in)-equality. It makes available two function: ==
and /=
. Most data types from the Standard Library have an instance of Eq
. As you already learned in 3 Data types, you can let the compiler automatically derive instances of Eq
for you using the deriving
keyword.
Templates always have an Eq
instance, and all types stored on a template need to have one.
Ord¶
The Ord
typeclass allows values of a type to be compared for order. It makes available functions: <
, >
, <=
, and >=
. Most of the inbuilt data types have an instance of Ord
. Furthermore, types like List
and Optional
get an instance of Ord
if the type they contain has one. You can let the compiler automatically derive instances of Ord
for you using the deriving
keyword.
Show¶
Show
indicates that a type can be serialized to Text
, ie “shown” in a shell. Its key function is show
, which takes a value and converts it to Text
. All inbuilt data types have an instance for Show
and types like List
and Optional
get an instance if the type they contain has one. It also supports the deriving
keyword.
Functor¶
Functors are the closest thing to “containers” that DAML has. Whenever you see a type with a single type parameter, you are probably looking at a Functor
: [a]
, Optional a
, Either Text a
, Update a
. Functors are things that can be mapped over and as such, the key function of Functor
is fmap
, which does generically what the map
function does for lists.
Other classic examples of Functors are Sets, Maps, Trees, etc.
Applicative Functor¶
Applicative Functors are a bit like Actions, which you met in 5 Adding constraints to a contract, except that you can’t use the result of one action as the input to another action. The only important Applicative Functor that isn’t an action in DAML is the Commands
type submitted in a submit
block in DAML Script. That’s why in order to use do
notation in DAML Script, you have to enable the ApplicativeDo
language extension.
Actions¶
Actions were already covered in 5 Adding constraints to a contract. One way to think of them is as “recipes” for a value, which need to be “executed to get at that value. Actions are always Functors (and Applicative Functors). The intuition for that is simply that fmap f x
is the recipe in x
with the extra instruction to apply the pure function f
to the result.
The really important Actions in DAML are Update
and Script
, but there are many others, like []
, Optional
, and Either a
.
Semigroups and Monoids¶
Semigroups and monoids are about binary operations, but in practice, their important use is for Text
and []
, where they allow concatenation using the {<>}
operator.
Additive and Multiplicative¶
Additive and Multiplicative abstract out arithmetic operations, so that (+)
, (-)
, (*)
, and some other functions can be used uniformly between Decimal
and Int
.
Important Modules in the Standard Library¶
For almost all the types and typeclasses presented above, the Standard Library contains a module:
- Module DA.List for Lists
- Module DA.Optional for
Optional
- Module DA.Tuple for Tuples
- Module DA.Either for
Either
- Module DA.Functor for Functors
- Module DA.Action for Actions
- Module DA.Monoid and Module DA.Semigroup for Monoids and Semigroups
- Module DA.Text for working with
Text
- Module DA.Time for working with
Time
- Module DA.Date for working with
Date
You get the idea, the names are fairly descriptive.
Other than the typeclasses defined in Prelude, there are two modules generalizing concepts you’ve already learnt about, which are worth knowing about: Foldable
and Traversable
. In Looping you learned all about folds and their Action equivalents. All the examples there were based on lists, but there are many other possible iterators. This is expressed in two additional typeclasses: Module DA.Traversable, and Module DA.Foldable. For more detail on these concepts, please refer to the literature in The Haskell Connection, or https://wiki.haskell.org/Foldable_and_Traversable.
Searching the Standard Library¶
Being able to browse the Standard Library starting from The standard library is a start, and the module naming helps, but it’s not an efficient process for finding out what a function you’ve encountered does, or even less so to find a function that does a thing you need to do.
DAML has it’s own version of the Hoogle search engine, which offers search both by name and by signature. It’s fully integrated into the search bar on https://docs.daml.com/, but for those wanting a pure Standard Library search, it’s also available on https://hoogle.daml.com.
Searching for functions by Name¶
Say you come across some functions you haven’t seen before, like the ones in the ensure
clause of the MultiTrade
.
ensure (length baseAssetCids == length baseAssets) &&
(length quoteApprovalCids == length quoteAssets) &&
not (null baseAssets) &&
not (null quoteAssets)
You may be able to guess what not
and null
do, but try searching those names in the documentation search. Search results from the Standard Library will show on top. not
, for example, gives
not
: Bool -> Bool
Boolean “not”
Signature (including type constraints) and description usually give a pretty clear picture of what a function does.
Searching for functions by Signature¶
The other very common use-case for the search is that you have some values that you want to do something with, but don’t know the standard library function you need. On the MultiTrade
template we have a list baseAssets
, and thanks to your ensure clause we know it’s non-empty. In the original Trade
we used baseAsset.owner
as the signatory. How do you get the first element of this list to extract the owner
without going through the motions of a complete pattern match using case
?
The trick is to think about the signature of the function that’s needed, and then to search for that signature. In this case, we want a single distinguished element from a list so the signature should be [a] -> a
. If you search for that, you’ll get a whole range of results, but again, Standard Library results are shown at the top.
Scanning the descriptions, head
is the obvious choice, as used in the let
of the MultiTrade
template.
You may notice that in the search results you also get some hits that don’t mention []
explicitly. For example:
The reason is that there is an instance for Foldable [a]
.
Let’s try another search. Suppose you didn’t want the first element, but the one at index n
. Remember that (!!)
operator from 9 Functional Programming 101? There are now two possible signatures we could search for: [a] -> Int -> a
and Int -> [a] -> a
. Try searching for both. You’ll see that the search returns (!!)
in both cases. You don’t have to worry about the order of arguments.
Next up¶
There’s little more to learn about writing DAML at this point that isn’t best learnt by practice and consulting reference material for both DAML and Haskell. To finish off this course, you’ll learn a little more about your options for testing and interacting with DAML code in 11 Testing DAML Contracts, and about the operational semantics of some keywords and common associated failures.