Applicative and monadic transactions in Daml

György Balázsi
DAML Masterclass
Published in
9 min readOct 26, 2021
Photo by 乐融 高 on Unsplash

All transactions in Daml share the basic trait of forming an atomic (all-or-nothing) collection of actions. Beyond this, there is a big difference: some of them consist of independent actions, some not.

There are two places where you can find transactions in Daml:

  • Do blocks in template choice bodies. This block contains all the consequences of a choice.
  • Optional do blocks after the submit keyword in Daml Script, as a notation for the Commands a data type, consisting of a series of commands. This imitates the submission of a possibly compound transaction through the Ledger API. (Submitting multiple actions as a transaction is not possible through the JSON API.)

For the latter, you need to enable the ApplicativeDo extension by adding {-# LANGUAGE ApplicativeDo #-} at the top of your file, see about this the docs.

What is the difference between these two kinds of transactions, and why we need the ApplicativeDo extension for the latter?

Two kinds of transactions in the Daml Ledger Model

Top-level actions in the Daml Ledger Model are actions requested directly by ledger parties. In contrast, consequences of a choice exercise are not top-level actions.

Corresponding to these two kinds of actions, we have two kinds of transactions.

The following example taken from the Daml docs, displaying a series of ledger commits, illustrates these two kinds of transactions:

See Causality and Local Ledgers

On one hand, Alice requested a series of top-level actions in one transaction commit: the creation of a CounterOffer contract, the creation of the ShowIou contract and the exercise of a choice on the created ShowIou contract. These actions are independent, none of them wants to use the returned result of another action. (Creating a contract and and exercising a choice on it in one go seems to violate the requirement of independence. In reality, these actions belong so strong together that there is a createAndExerciseCmd command for requesting them together.)

These top-level actions, requested in one go, form a transaction consisting of independent actions. Notwithstanding that the actions must be independent, the transaction still forms an atomic unit. If any of the actions wrapped together fails, nothing is committed to the ledger.

On the other hand, the consequences of a choice form a transaction, this time the consequences of the Painter accepting Alice’s counteroffer.

The consequences of a choice also form an atomic transaction, meaning that the execution of its actions happens atomically, with the added benefit that the actions can use the values returned another action in the transaction. In other words: this kind of transaction doesn’t need to consist of independent actions.

The representation of the two kinds of transactions (independent actions, not necessarily independent actions) in the Daml surface syntax happens using two type classes prevalent in functional languages: the Applicative and the Monad type class.

The syntax of Daml reflects the difference in the aforementioned two kinds ofdo notation, both coming from its parent language, Haskell.

The “classic” do notation in Daml stands for monadic composition. The do block is actually syntactic sugar on the “bind” operator (see the >>= operator). The bind operator makes it possible to combine actions similarly to “piping”: the result returned from one action can be “piped” into the next action.

The Applicative do notation came later, in order to make the otherwise cryptic applicative notation more readable. In order to use the applicative do notation, we need to use the aforementioned language extension. The Applicative type class doesn’t have the “bind” operator and and cannot pipe computation results one into the other. It just makes sure that if the computation of one input of a combined computation fails, the whole combination fails.

The Applicative and the Monad type class in Daml (and Haskell)

In order to make de difference between the Applicative and the Monad type class more clear, I will show some examples using the Daml REPL. (Very similar examples could be shown using Haskell, with some minor differences.)

Let’s go step by step, by starting with some basic concepts.

Partial application

Haskell-like languages use a syntax for function application which is similar to command line submissions. It’s called prefix notation. First comes the function name, thereafter the arguments, separated by spaces. (Infix operators can be transformed into functions using brackets.) So the following notation stands for adding to integers:

daml> (+) 2 35

In these languages, under the hood, functions always have one argument. A function with two arguments like addition is applied in two steps. The application of the function to the first argument creates another function, which has one argument, the remaining one. And applying the last argument creates actually the result of the computation.

Applying only a subset of the arguments is called “partial application”, and it’s a way of creating new functions from existing ones. Using the example below, we can create a function which adds 2 to every integer:

daml> (+2) 35daml> (+2) 46daml> (+2) 57

The function application operator

There is an alternative syntax for function application, which can come in handy in some situations, e.g. for avoiding the use of too much brackets. This is the $ operator.

In the following example, first we perform the calculation at the right hand side of the $ operator, namely multiply 3 and 4, and then apply the (+2) function, without using brackets for indicating operation precedence:

daml> (+2) $ 3*414

Type classes

A type class or parametric type can be thought of as a “box”, containing (or not containing) a value of some specific type.

Why would you want to use such boxes?

One reason is to handle possibly unsuccessful computations. The box can either contain some concrete value, representing the result of a successful computation. Or the box can be empty, representing the missing result of an unsuccessful computation. (Note that the “empty box” still has a type, reflecting the type of value which could be inside.)

In this way we can avoid using a generic null value, which causes a lot of headaches.

The basic difference between type classes is made up by the functions they implement. So let’s look at some of them a bit more closely.

The Functor type class

The Functor type class needs to have an operator which performs some kind of mapping.

In the case of a very well known Functor, the list type class, this is the plain old map function. The following example expresses that we want to add 2 to the elements of a list of integers:

daml> map (+2) [1,2,3][3,4,5]

In order to highlight that mapping is in a way similar to function application, with the difference that we want to apply a function to values within a “box”, there is an alternative syntax which is similar to the $ function application operator (being an inflix operator in contrast to the map prefix operator):

daml> (+2) <$> [1,2,3][3,4,5]

The generalization of the map operator to other kinds of “boxes” is the fmap operator. For lists, fmap does the same as map, but fmap has the added benefit that it can be used with other kinds of “boxes” as well, e.g. with the Optional type class:

daml> fmap (+2) [1,2,3][3,4,5]daml> fmap (+2) (Some 3)Some 5

The “function application for values within a box” syntax works similarly:

daml> (+2) <$> [1,2,3][3,4,5]daml> (+2) <$> (Some 3)Some 5

If the “box” is empty, the result will also be an empty box, no matter what function we want to apply to the values within it:

daml> (+2) <$> [][]daml> (+2) <$> NoneNone

The Applicative type class

The Applicative type class has an additional operator, the <*> operator. This operator makes it possible to combine “boxed” values into another “boxed” value, using functions with at least two arguments, and the fmap or the <$> operator. Formally, the <$> operator indicates the function application, and the <*> separates the arguments. In order to demonstrate this, we need a binary function, so we go back to the addition function, without partial application.

daml> (+) <$> [2] <*> [3][5]daml> (+) <$> Some 2 <*> Some 3Some 5

Empty “boxes” behave as expected: if any of the “boxed” inputs is missing, the result is also an empty box:

daml> (+) <$> [] <*> [3][]daml> (+) <$> [2] <*> [][]daml> (+) <$> None <*> Some 3Nonedaml> (+) <$> Some 2 <*> NoneNone

Another interpretation is that the <*> operator applies a “boxed” function to a “boxed” value and returns a “boxed” value.

daml> (Some (+2)) <*> (Some 3)Some 5daml> (Some (+2)) <*> NoneNonedaml>  (None: Optional (Int -> Int)) <*> (Some 3)None

(In the last example we had to indicate that this a None value which could contain a function, where the function takes an Int value and also returns an Int value. Daml, like Hskell, has type inference, so it sometimes can find out which type a None value has, but this is not always the case.)

The Monad type class

The “boxed” inputs to the addition function in the previous example need to be independent from each other. Using the <*> operator we cannot “pipe” the result of one computation into another.

This kind of “piping” is the job of the “bind” operator ( >>= ), which is added to Applicatives in order to make them Monads, preserving the “box”-like behavior (roughly, excuse me for the somewhat sketchy explanation).

In order to demonstrate the “bind” operator, we need a monadic function, meaning a function which creates from a “naked” value a “boxed” value. In the following example, we use a monadic functin which tries to half an integer. If the integer is even, the function halves it and puts the result into a “box”. If the integer is odd, returns an empty box. Starting with 4, we can perform halving twice successfully, and the third time (and for every attempt afterwards) we get an empty “box”:

daml> let halfIfEven x = if x % 2 == 0 then (Some (x/2)) else Nonedaml> Some 4 >>= halfIfEvenSome 2daml> Some 4 >>= halfIfEven >>= halfIfEvenSome 1daml> Some 4 >>= halfIfEven >>= halfIfEven >>= halfIfEvenNonedaml>

Commands and actions in Daml ledgers

After this detour, let’s go back to Daml and see how the basic functional type classes, Applicative and Monad are baked into the surface syntax.

Let’s start with Monad.

In Daml lingo, the Monad type class is called Action. The Update a type class e.g. is an instance of Action (there are others, too), and expresses an action which tries (and sometimes fails) to update or query the ledger, before returning a value of type a. Examples include createand fetch. The create action returns the contract id of the created contract, if the creation is successful, the fetch action returns the payload of the contract, if the contract corresponding to the specified contract id exists.

In a choice body, these actions can be “piped” into one another, meaning, that steps of the transaction can use the returned values from previous steps. So these steps don’t need to be independent.

On the other hand, the Commands a type class, which represents transactions submitted through the Ledger API, is an instance of Applicative, but it’s not an Action (Monad). The actions wrapped up in such a transaction need to be independent, so the “bind” or “piping” operator is not implemented for it. This reflects the fact that through the Daml Ledger API non piping is possible between the elements of a transaction.

Do notation

In Daml, we don’t often use the aforementioned <$>, <*> and >>= operators. Instead, we use do notation which is more readable and easy to reason about. The connection between the Applicative and Monad operators with the do block notation is explained e.g. in the following articles:

Daml code example

As a wrap up, let’s see some sample code, highlighting the two kinds of transactions, written in two different styles.

The first snippet is some Daml code showcasing the two types of transactions, the monadic transaction in the Transfer choice body, and the applicative transaction in the test script, as an applicative do block after the submit command.

Writing Daml code like this is good practice.

The other snippet is the same code, without using applicative do notation for the applicative transaction in the Script, and using the “bind” operator for “piping” actions one into the other in the choice body.

Writing Daml code like this is not good practice. I just show it to highlight the fact that the do block notation is syntactic sugar for other constructs.

Not mentioned before, but building up the applicative transaction we are using the sequence function which converts a list of independent actions into one action, that is a transaction. More about the sequence function here.

--

--