# 3 Data types¶

In 1 Basic contracts, you learnt about contract templates, which specify the types of contracts that can be created on the ledger, and what data those contracts hold in their arguments.

In 2 Testing templates using Daml Script, you learnt about the script view in Daml Studio, which displays the current ledger state. It shows one table per template, with one row per contract of that type and one column per field in the arguments.

This actually provides a useful way of thinking about templates: like tables in databases. Templates specify a data schema for the ledger:

• each template corresponds to a table
• each field in the with block of a template corresponds to a column in that table
• each contract of that type corresponds to a table row

In this section, you’ll learn how to create rich data schemas for your ledger. Specifically you’ll learn about:

• Daml’s built-in and native data types
• Record types
• Derivation of standard properties
• Variants
• Manipulating immutable data
• Contract keys

After this section, you should be able to use a Daml ledger as a simple database where individual parties can write, read and delete complex data.

Hint

Remember that you can load all the code for this section into a folder called 3_Data by running daml new 3_Data --template daml-intro-3

## Native types¶

You have already encountered a few native Daml types: Party in 1 Basic contracts, and Text and ContractId in 2 Testing templates using Daml Script. Here are those native types and more:

• Party Stores the identity of an entity that is able to act on the ledger, in the sense that they can sign contracts and submit transactions. In general, Party is opaque.
• Text Stores a unicode character string like "Alice".
• ContractId a Stores a reference to a contract of type a.
• Int Stores signed 64-bit integers. For example, -123.
• Decimal Stores fixed-point number with 28 digits before and 10 digits after the decimal point. For example, 0.0000000001 or -9999999999999999999999999999.9999999999.
• Bool Stores True or False.
• Date Stores a date.
• Time Stores absolute UTC time.
• RelTime Stores a difference in time.

The below script instantiates each one of these types, manipulates it where appropriate, and tests the result.

import Daml.Script
import DA.Time
import DA.Date

native_test = script do
alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
let
my_int = -123
my_dec = 0.001 : Decimal
my_text = "Alice"
my_bool = False
my_date = date 2020 Jan 01
my_time = time my_date 00 00 00
my_rel_time = hours 24

assert (alice /= bob)
assert (-my_int == 123)
assert (1000.0 * my_dec == 1.0)
assert (my_text == "Alice")
assert (not my_bool)
assert (addDays my_date 1 == date 2020 Jan 02)
assert (addRelTime my_time my_rel_time == time (addDays my_date 1) 00 00 00)


Despite its simplicity, there are quite a few things to note in this script:

• The import statements at the top import two packages from the Daml Standard Library, which contain all the date and time related functions we use here as well as the functions used in Daml Scripts. More on packages, imports and the standard library later.

• Most of the variables are declared inside a let block.

That’s because the script do block expects script actions like submit or Party. An integer like 123 is not an action, it’s a pure expression, something we can evaluate without any ledger. You can think of the let as turning variable declaration into an action.

• Most variables do not have annotations to say what type they are.

That’s because Daml is very good at inferring types. The compiler knows that 123 is an Int, so if you declare my_int = 123, it can infer that my_int is also an Int. This means you don’t have to write the type annotation my_int : Int = 123.

However, if the type is ambiguous so that the compiler can’t infer it, you do have to add a type annotation. This is the case for 0.001 which could be any Numeric n. Here we specify 0.001 : Decimal which is a synonym for Numeric 10. You can always choose to add type annotations to aid readability.

• The assert function is an action that takes a boolean value and succeeds with True and fails with False.

Try putting assert False somewhere in a script and see what happens to the script result.

With templates and these native types, it’s already possible to write a schema akin to a table in a relational database. Below, Token is extended into a simple CashBalance, administered by a party in the role of an accountant.

template CashBalance
with
accountant : Party
currency : Text
amount : Decimal
owner : Party
account_number : Text
bank : Party
bank_telephone : Text
where
signatory accountant

cash_balance_test = script do
accountant <- allocateParty "Bob"
alice <- allocateParty "Alice"
bob <- allocateParty "Bank of Bob"

submit accountant do
createCmd CashBalance with
accountant
currency = "USD"
amount = 100.0
owner = alice
account_number = "ABC123"
bank = bob
bank_telephone = "012 3456 789"


## Assembling types¶

There’s quite a lot of information on the CashBalance above and it would be nice to be able to give that data more structure. Fortunately, Daml’s type system has a number of ways to assemble these native types into much more expressive structures.

### Tuples¶

A common task is to group values in a generic way. Take, for example, a key-value pair with a Text key and an Int value. In Daml, you could use a two-tuple of type (Text, Int) to do so. If you wanted to express a coordinate in three dimensions, you could group three Decimal values using a three-tuple (Decimal, Decimal, Decimal).

import DA.Tuple
import Daml.Script

tuple_test = script do
let
my_key_value = ("Key", 1)
my_coordinate = (1.0 : Decimal, 2.0 : Decimal, 3.0 : Decimal)

assert (fst my_key_value == "Key")
assert (snd my_key_value == 1)
assert (my_key_value._1 == "Key")
assert (my_key_value._2 == 1)

assert (my_coordinate == (fst3 my_coordinate, snd3 my_coordinate, thd3 my_coordinate))
assert (my_coordinate == (my_coordinate._1, my_coordinate._2, my_coordinate._3))


You can access the data in the tuples using:

• functions fst, snd, fst3, snd3, thd3
• a dot-syntax with field names _1, _2, _3, etc.

Daml supports tuples with up to 20 elements, but accessor functions like fst are only included for 2- and 3-tuples.

### Lists¶

Lists in Daml take a single type parameter defining the type of thing in the list. So you can have a list of integers [Int] or a list of strings [Text], but not a list mixing integers and strings.

That’s because Daml is statically and strongly typed. When you get an element out of a list, the compiler needs to know what type that element has.

The below script instantiates a few lists of integers and demonstrates the most important list functions.

import DA.List
import Daml.Script

list_test = script do
let
empty : [Int] = []
one = [1]
two = [2]
many = [3, 4, 5]

-- head gets the first element of a list

-- tail gets the remainder after head
assert (tail one == empty)
assert (tail many == [4, 5])

-- ++ concatenates lists
assert (one ++ two ++ many == [1, 2, 3, 4, 5])
assert (empty ++ many ++ empty == many)

-- :: adds an element to the beginning of a list.
assert (1 :: 2 :: 3 :: 4 :: 5 :: empty == 1 :: 2 :: many)


Note the type annotation on empty : [Int] = []. It’s necessary because [] is ambiguous. It could be a list of integers or of strings, but the compiler needs to know which it is.

### Records¶

You can think of records as named tuples with named fields. Declare them using the data keyword: data T = C with, where T is the type name and C is the data constructor. In practice, it’s a good idea to always use the same name for type and data constructor.

data MyRecord = MyRecord with
my_txt : Text
my_int : Int
my_dec : Decimal
my_list : [Text]

-- Fields of same type can be declared in one line
data Coordinate = Coordinate with
x, y, z : Decimal

-- Custom data types can also have variables
data KeyValue k v = KeyValue with
my_key : k
my_val : v

data Nested = Nested with
my_coord : Coordinate
my_record : MyRecord
my_kv : KeyValue Text Int

record_test = script do
let
my_record = MyRecord with
my_txt = "Text"
my_int = 2
my_dec = 2.5
my_list = ["One", "Two", "Three"]

my_coord = Coordinate with
x = 1.0
y = 2.0
z = 3.0

-- my_text_int has type KeyValue Text Int
my_text_int = KeyValue with
my_key = "Key"
my_val = 1

-- my_int_decimal has type KeyValue Int Decimal
my_int_decimal = KeyValue with
my_key = 2
my_val = 2.0 : Decimal

-- If variables are in scope that match field names, we can pick them up
-- implicitly, writing just my_coord instead of my_coord = my_coord.
my_nested = Nested with
my_coord
my_record
my_kv = my_text_int

-- Fields can be accessed with dot syntax
assert (my_coord.x == 1.0)
assert (my_text_int.my_key == "Key")
assert (my_nested.my_record.my_dec == 2.5)


You’ll notice that the syntax to declare records is very similar to the syntax used to declare templates. That’s no accident because a template is really just a special record. When you write template Token with, one of the things that happens in the background is that this becomes a data Token = Token with.

In the assert statements above, we always compared values of in-built types. If you wrote assert (my_record == my_record) in the script, you may be surprised to get an error message No instance for (Eq MyRecord) arising from a use of ‘==’. Equality in Daml is always value equality and we haven’t written a function to check value equality for MyRecord values. But don’t worry, you don’t have to implement this rather obvious function yourself. The compiler is smart enough to do it for you, if you use deriving (Eq):

data EqRecord = EqRecord with
my_txt : Text
my_int : Int
my_dec : Decimal
my_list : [Text]
deriving (Eq)

data MyContainer a = MyContainer with
contents : a
deriving (Eq)

eq_test = script do
let
eq_record = EqRecord with
my_txt = "Text"
my_int = 2
my_dec = 2.5
my_list = ["One", "Two", "Three"]

my_container = MyContainer with
contents = eq_record
other_container = MyContainer with
contents = eq_record

assert(my_container.contents == eq_record)
assert(my_container == other_container)


Eq is what is called a typeclass. You can think of a typeclass as being like an interface in other languages: it is the mechanism by which you can define a set of functions (for example, == and /= in the case of Eq) to work on multiple types, with a specific implementation for each type they can apply to.

There are some other typeclasses that the compiler can derive automatically. Most prominently, Show to get access to the function show (equivalent to toString in many languages) and Ord, which gives access to comparison operators <, >, <=, >=.

It’s a good idea to always derive Eq and Show using deriving (Eq, Show). The record types created using template T with do this automatically, and the native types have appropriate typeclass instances. Eg Int derives Eq, Show and Ord, and ContractId a derives Eq and Show.

Records can give the data on CashBalance a bit more structure:

data Bank = Bank with
party : Party
telephone : Text
deriving (Eq, Show)

data Account = Account with
owner : Party
number : Text
bank : Bank
deriving (Eq, Show)

data Cash = Cash with
currency : Text
amount : Decimal
deriving (Eq, Show)

template CashBalance
with
accountant : Party
cash : Cash
account : Account
where
signatory accountant

cash_balance_test = script do
accountant <- allocateParty "Bob"
owner <- allocateParty "Alice"
bank_party <- allocateParty "Bank"
let
bank = Bank with
party = bank_party
telephone = "012 3456 789"
account = Account with
owner
bank
number = "ABC123"
cash = Cash with
currency = "USD"
amount = 100.0

submit accountant do
createCmd CashBalance with
accountant
cash
account
pure ()


If you look at the resulting script view, you’ll see that this still gives rise to one table. The records are expanded out into columns using dot notation.

### Variants and pattern matching¶

Suppose now that you also wanted to keep track of cash in hand. Cash in hand doesn’t have a bank, but you can’t just leave bank empty. Daml doesn’t have an equivalent to null. Variants can express that cash can either be in hand or at a bank.

data Bank = Bank with
party : Party
telephone : Text
deriving (Eq, Show)

data Account = Account with
number : Text
bank : Bank
deriving (Eq, Show)

data Cash = Cash with
currency : Text
amount : Decimal
deriving (Eq, Show)

data Location
= InHand
| InAccount Account
deriving (Eq, Show)

template CashBalance
with
accountant : Party
owner : Party
cash : Cash
location : Location
where
signatory accountant

cash_balance_test = do
accountant <- allocateParty "Bob"
owner <- allocateParty "Alice"
bank_party <- allocateParty "Bank"
let
bank = Bank with
party = bank_party
telephone = "012 3456 789"
account = Account with
bank
number = "ABC123"
cash = Cash with
currency = "USD"
amount = 100.0

submit accountant do
createCmd CashBalance with
accountant
owner
cash
location = InHand

submit accountant do
createCmd CashBalance with
accountant
owner
cash
location = InAccount account


The way to read the declaration of Location is “A Location either has value InHand OR has a value InAccount a where a is of type Account”. This is quite an explicit way to say that there may or may not be an Account associated with a CashBalance and gives both cases suggestive names.

Another option is to use the built-in Optional type. The None value of type Optional a is the closest Daml has to a null value:

data Optional a
= None
| Some a
deriving (Eq, Show)


Variant types where none of the data constructors take a parameter are called enums:

data DayOfWeek
= Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| Saturday
| Sunday
deriving (Eq, Show)


To access the data in variants, you need to distinguish the different possible cases. For example, you can no longer access the account number of a Location directly, because if it is InHand, there may be no account number.

To do this, you can use pattern matching and either throw errors or return compatible types for all cases:

{-
-- Commented out as Either is defined in the standard library.
data Either a b
= Left a
| Right b
-}

variant_access_test = script do
let
l : Either Int Text = Left 1
r : Either Int Text = Right "r"

-- If we know that l is a Left, we can error on the Right case.
l_value = case l of
Left i -> i
Right i -> error "Expecting Left"
-- Comment out at your own peril
{-
r_value = case r of
Left i -> i
Right i -> error "Expecting Left"
-}

-- If we are unsure, we can return an Optional in both cases
ol_value = case l of
Left i -> Some i
Right i -> None
or_value = case r of
Left i -> Some i
Right i -> None

-- If we don't care about values or even constructors, we can use wildcards
l_value2 = case l of
Left i -> i
Right _ -> error "Expecting Left"
l_value3 = case l of
Left i -> i
_ -> error "Expecting Left"

day = Sunday
weekend = case day of
Saturday -> True
Sunday -> True
_ -> False

assert (l_value == 1)
assert (l_value2 == 1)
assert (l_value3 == 1)
assert (ol_value == Some 1)
assert (or_value == None)
assert weekend


## Manipulating data¶

You’ve got all the ingredients to build rich types expressing the data you want to be able to write to the ledger, and you have seen how to create new values and read fields from values. But how do you manipulate values once created?

All data in Daml is immutable, meaning once a value is created, it will never change. Rather than changing values, you create new values based on old ones with some changes applied:

manipulation_demo = script do
let
eq_record = EqRecord with
my_txt = "Text"
my_int = 2
my_dec = 2.5
my_list = ["One", "Two", "Three"]

-- A verbose way to change eq_record
changed_record = EqRecord with
my_txt = eq_record.my_txt
my_int = 3
my_dec = eq_record.my_dec
my_list = eq_record.my_list

-- A better way
better_changed_record = eq_record with
my_int = 3

record_with_changed_list = eq_record with
my_list = "Zero" :: eq_record.my_list

assert (eq_record.my_int == 2)
assert (changed_record == better_changed_record)

-- The list on eq_record can't be changed.
assert (eq_record.my_list == ["One", "Two", "Three"])
-- The list on record_with_changed_list is a new one.
assert (record_with_changed_list.my_list == ["Zero", "One", "Two", "Three"])


changed_record and better_changed_record are each a copy of eq_record with the field my_int changed. better_changed_record shows the recommended way to change fields on a record. The syntax is almost the same as for a new record, but the record name is replaced with the old value: eq_record with instead of EqRecord with. The with block no longer needs to give values to all fields of EqRecord. Any missing fields are taken from eq_record.

Throughout the script, eq_record never changes. The expression "Zero" :: eq_record.my_list doesn’t change the list in-place, but creates a new list, which is eq_record.my_list with an extra element in the beginning.

## Contract keys¶

Daml’s type system lets you store richly structured data on Daml templates, but just like most database schemas have more than one table, Daml contract models often have multiple templates that reference each other. For example, you may not want to store your bank and account information on each individual cash balance contract, but instead store those on separate contracts.

You have already met the type ContractId a, which references a contract of type a. The below shows a contract model where Account is split out into a separate template and referenced by ContractId, but it also highlights a big problem with that kind of reference: just like data, contracts are immutable. They can only be created and archived, so if you want to change the data on a contract, you end up archiving the original contract and creating a new one with the changed data. That makes contract IDs very unstable, and can cause stale references.

data Bank = Bank with
party : Party
telephone : Text
deriving (Eq, Show)

template Account
with
accountant : Party
owner : Party
number : Text
bank : Bank
where
signatory accountant

data Cash = Cash with
currency : Text
amount : Decimal
deriving (Eq, Show)

template CashBalance
with
accountant : Party
cash : Cash
account : ContractId Account
where
signatory accountant

id_ref_test = do
accountant <- allocateParty "Bob"
owner <- allocateParty "Alice"
bank_party <- allocateParty "Bank"
let
bank = Bank with
party = bank_party
telephone = "012 3456 789"
cash = Cash with
currency = "USD"
amount = 100.0

accountCid <- submit accountant do
createCmd Account with
accountant
owner
bank
number = "ABC123"

balanceCid <- submit accountant do
createCmd CashBalance with
accountant
cash
account = accountCid

-- Now the accountant updates the telephone number for the bank on the account
Some account <- queryContractId accountant accountCid
new_account <- submit accountant do
archiveCmd accountCid
createCmd account with
bank = account.bank with
telephone = "098 7654 321"
pure ()

-- The account field on the balance now refers to the archived
-- contract, so this will fail.
Some balance <- queryContractId accountant balanceCid
optAccount <- queryContractId accountant balance.account
optAccount === None


The script above uses the queryContractId function, which retrieves the arguments of an active contract using its contract ID. If there is no active contract with the given identifier visible to the given party, queryContractId returns None. Here, we use a pattern match on Some which will abort the script if queryContractId returns None.

Note that, for the first time, the party submitting a transaction is doing more than one thing as part of that transaction. To create new_account, the accountant archives the old account and creates a new account, all in one transaction. More on building transactions in 7 Composing choices.

You can define stable keys for contracts using the key and maintainer keywords. key defines the primary key of a template, with the ability to look up contracts by key, and a uniqueness constraint in the sense that only one contract of a given template and with a given key value can be active at a time.

data Bank = Bank with
party : Party
telephone : Text
deriving (Eq, Show)

data AccountKey = AccountKey with
accountant : Party
number : Text
bank_party : Party
deriving (Eq, Show)

template Account
with
accountant : Party
owner : Party
number : Text
bank : Bank
where
signatory accountant

key AccountKey with
accountant
number
bank_party = bank.party
: AccountKey
maintainer key.accountant

data Cash = Cash with
currency : Text
amount : Decimal
deriving (Eq, Show)

template CashBalance
with
accountant : Party
cash : Cash
account : AccountKey
where
signatory accountant

id_ref_test = do
accountant <- allocateParty "Bob"
owner <- allocateParty "Alice"
bank_party <- allocateParty "Bank"
let
bank = Bank with
party = bank_party
telephone = "012 3456 789"
cash = Cash with
currency = "USD"
amount = 100.0

accountCid <- submit accountant do
createCmd Account with
accountant
owner
bank
number = "ABC123"

Some account <- queryContractId accountant accountCid
balanceCid <- submit accountant do
createCmd CashBalance with
accountant
cash
account = key account

-- Now the accountant updates the telephone number for the bank on the account
Some account <- queryContractId accountant accountCid
new_accountCid <- submit accountant do
archiveCmd accountCid
cid <- createCmd account with
bank = account.bank with
telephone = "098 7654 321"
pure cid

-- Thanks to contract keys, the current account contract is fetched
Some balance <- queryContractId accountant balanceCid
(cid, account) <- submit accountant do
createAndExerciseCmd (Helper accountant) (FetchAccountByKey balance.account)
assert (cid == new_accountCid)

-- Helper template to call fetchByKey.
template Helper
with
p : Party
where
signatory p
choice FetchAccountByKey : (ContractId Account, Account)
with
accountKey : AccountKey
controller p
do fetchByKey @Account accountKey


Since Daml is designed to run on distributed systems, you have to assume that there is no global entity that can guarantee uniqueness, which is why each key expression must come with a maintainer expression. maintainer takes one or several parties, all of which have to be signatories of the contract and be part of the key. That way the index can be partitioned amongst sets of maintainers, and each set of maintainers can independently ensure the uniqueness constraint on their piece of the index. The constraint that maintainers are part of the key is ensured by only having the variable key in each maintainer expression.

Instead of calling queryContractId to get the contract arguments associated with a given contract identifier, we use fetchByKey @Account. fetchByKey @Account takes a value of type AccountKey and returns a tuple (ContractId Account, Account) if the lookup was successful or fails the transaction otherwise. fetchByKey cannot be used directly in the list of commands sent to the ledger. Therefore we create a Helper template with a FetchAccountByKey choice and call that via createAndExerciseCmd. We will learn more about choices in the next section.

Since a single type could be used as the key for multiple templates, you need to tell the compiler what type of contract is being fetched by using the @Account notation.

## Next up¶

You can now define data schemas for the ledger, read, write and delete data from the ledger, and use keys to reference and look up data in a stable fashion.

In 4 Transforming data using choices you’ll learn how to define data transformations and give other parties the right to manipulate data in restricted ways.