ligo/gitlab-pages/docs/language-basics/maps-records.md

535 lines
12 KiB
Markdown
Raw Normal View History

---
id: maps-records
title: Maps, Records
---
2020-01-28 18:13:50 +04:00
So far we have seen pretty basic data types. LIGO also offers more
complex built-in constructs, such as maps and records.
## Maps
2020-01-28 18:13:50 +04:00
Maps are natively available in Michelson, and LIGO builds on top of
them. A requirement for a map is that its keys be of the same type,
and that type must be comparable.
2020-01-28 18:13:50 +04:00
Here is how a custom map type is defined:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
type move is int * int
type moveset is map(address, move)
```
<!--CameLIGO-->
```cameligo
type move = int * int
type moveset = (address, move) map
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
type move = (int, int);
type moveset = map(address, move);
2019-12-10 17:47:31 +04:00
```
<!--END_DOCUSAURUS_CODE_TABS-->
2020-01-28 18:13:50 +04:00
And here is how a map value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const moves: moveset = map
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address) -> (1, 2);
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) -> (0, 3);
end
```
> Notice the `->` between the key and its value and `;` to separate individual map entries.
>
> `("<string value>": address)` means that we type-cast a string into an address.
<!--CameLIGO-->
```cameligo
let moves: moveset = Map.literal
[ (("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address), (1, 2)) ;
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), (0, 3)) ;
]
```
> Map.literal constructs the map from a list of key-value pair tuples, `(<key>, <value>)`.
> Note also the `;` to separate individual map entries.
>
> `("<string value>": address)` means that we type-cast a string into an address.
2019-12-10 17:47:31 +04:00
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let moves : moveset =
2019-12-10 17:47:31 +04:00
Map.literal([
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address, (1, 2)),
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, (0, 3)),
2019-12-10 17:47:31 +04:00
]);
2019-12-11 13:34:08 +04:00
```
2019-12-10 17:47:31 +04:00
> Map.literal constructs the map from a list of key-value pair tuples, `(<key>, <value>)`.
>
> `("<string value>": address)` means that we type-cast a string into an address.
2019-12-11 13:34:08 +04:00
<!--END_DOCUSAURUS_CODE_TABS-->
### Accessing map values by key
2020-01-28 18:13:50 +04:00
If we want to access a move from our moveset above, we can use the
`[]` operator/accessor to read the associated `move` value. However,
the value we will get will be wrapped as an optional; in our case
`option(move)`. Here is an example:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const my_balance : option(move) = moves[("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)];
```
<!--CameLIGO-->
```cameligo
let my_balance : move option = Map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
2019-12-10 17:47:31 +04:00
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let my_balance : option(move) =
Map.find_opt("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
2019-12-10 17:47:31 +04:00
```
<!--END_DOCUSAURUS_CODE_TABS-->
#### Obtaining a map value forcefully
Accessing a value in a map yields an option, however you can also get the value directly:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const my_balance : move = get_force(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), moves);
```
<!--CameLIGO-->
```cameligo
let my_balance : move = Map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let my_balance : move =
Map.find("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
2019-12-10 17:47:31 +04:00
```
<!--END_DOCUSAURUS_CODE_TABS-->
### Updating the contents of a map
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
The values of a PascaLIGO map can be updated using the ordinary assignment syntax:
```pascaligo
function set_ (var m: moveset) : moveset is
block {
m[("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)] := (4,9);
} with m
```
<!--Cameligo-->
We can update a map in CameLIGO using the `Map.update` built-in:
```cameligo
let updated_map: moveset = Map.update ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) (Some (4,9)) moves
```
<!--Reasonligo-->
We can update a map in ReasonLIGO using the `Map.update` built-in:
```reasonligo
let updated_map: moveset = Map.update(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), Some((4,9)), moves);
```
<!--END_DOCUSAURUS_CODE_TABS-->
### Iteration over the contents of a map
There are three kinds of iteration on LIGO maps, `iter`, `map` and `fold`. `iter`
is an iteration over the map with no return value, its only use is to
generate side effects. This can be useful if for example you would like to check
that each value inside of a map is within a certain range, with an error thrown
otherwise.
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
function iter_op (const m : moveset) : unit is
block {
function aggregate (const i : address ; const j : move) : unit is block
2020-01-28 18:13:50 +04:00
{ if j.1 > 1 then skip else failwith("fail") } with unit
} with map_iter(aggregate, m);
```
<!--CameLIGO-->
```cameligo
let iter_op (m : moveset) : unit =
let assert_eq = fun (i,j: address * move) -> assert (j.0 > 1)
in Map.iter assert_eq m
```
2019-12-10 17:47:31 +04:00
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let iter_op = (m: moveset): unit => {
2020-01-28 18:13:50 +04:00
let assert_eq = ((i,j): (address, move)) => assert (j[0] > 1);
2019-12-10 17:47:31 +04:00
Map.iter(assert_eq, m);
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
`map` is a way to create a new map by modifying the contents of an existing one.
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
function map_op (const m : moveset) : moveset is
block {
2020-01-28 18:13:50 +04:00
function increment (const i : address ; const j : move) : move is (j.0, j.1 + 1);
} with map_map (increment, m);
```
<!--CameLIGO-->
```cameligo
let map_op (m : moveset) : moveset =
let increment = fun (i,j: address * move) -> (j.0, j.1 + 1)
in Map.map increment m
```
2019-12-10 17:47:31 +04:00
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let map_op = (m: moveset): moveset => {
let increment = ((i,j): (address, move)) => (j[0], j[1] + 1);
2019-12-10 17:47:31 +04:00
Map.map(increment, m);
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
2020-01-28 18:13:50 +04:00
`fold` is an aggregation function that return the combination of a
maps contents.
2020-01-28 18:13:50 +04:00
The fold is a loop which extracts an element of the map on each
iteration. It then provides this element and an existing value to a
folding function which combines them. On the first iteration, the
existing value is an initial expression given by the programmer. On
each subsequent iteration it is the result of the previous iteration.
It eventually returns the result of combining all the elements.
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
function fold_op (const m : moveset) : int is
block {
2020-01-28 18:13:50 +04:00
function aggregate (const j : int; const cur : address * (int * int)) : int is j + cur.1.1
} with map_fold(aggregate, m, 5)
```
<!--CameLIGO-->
```cameligo
let fold_op (m : moveset) : moveset =
2020-01-28 18:13:50 +04:00
let aggregate = fun (i,j: int * (address * (int * int))) -> i + j.1.1
in Map.fold aggregate m 5
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let fold_op = (m: moveset): moveset => {
let aggregate = ((i,j): (int, (address, (int,int)))) => i + j[1][1];
Map.fold(aggregate, m, 5);
2019-12-10 17:47:31 +04:00
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Big Maps
Ordinary maps are fine for contracts with a finite lifespan or a bounded number
of users. For many contracts however, the intention is to have a map hold *many*
entries, potentially millions or billions. The cost of loading these entries into
the environment each time a user executes the contract would eventually become
too expensive were it not for big maps. Big maps are a data structure offered by
Tezos which handles the scaling concerns for us. In LIGO, the interface for big
maps is analogous to the one used for ordinary maps.
2020-01-28 18:13:50 +04:00
Here is how we define a big map:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
type move is (int * int)
type moveset is big_map (address, move)
```
<!--CameLIGO-->
```cameligo
type move = int * int
type moveset = (address, move) big_map
```
<!--ReasonLIGO-->
```reasonligo
type move = (int, int);
type moveset = big_map(address, move);
```
<!--END_DOCUSAURUS_CODE_TABS-->
2020-01-28 18:13:50 +04:00
And here is how a map value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
const moves: moveset =
big_map
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address) -> (1,2);
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) -> (0,3);
end
```
> Notice the `->` between the key and its value and `;` to separate individual map entries.
>
> `("<string value>": address)` means that we type-cast a string into an address.
<!--CameLIGO-->
```cameligo
2020-01-28 18:13:50 +04:00
let moves: moveset =
Big_map.literal [
(("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address), (1,2));
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), (0,3));
]
```
> Big_map.literal constructs the map from a list of key-value pair tuples, `(<key>, <value>)`.
> Note also the `;` to separate individual map entries.
>
2020-01-28 18:13:50 +04:00
> `("<string value>": address)` means that we cast a string into an address.
<!--ReasonLIGO-->
```reasonligo
let moves: moveset =
2020-01-28 18:13:50 +04:00
Big_map.literal ([
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address, (1,2)),
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, (0,3)),
]);
```
> Big_map.literal constructs the map from a list of key-value pair tuples, `(<key>, <value>)`.
>
2020-01-28 18:13:50 +04:00
> `("<string value>": address)` means that we cast a string into an address.
<!--END_DOCUSAURUS_CODE_TABS-->
### Accessing map values by key
2020-01-28 18:13:50 +04:00
If we want to access a move from our moveset above, we can use the
`[]` operator/accessor to read the associated `move` value. However,
the value we will get will be wrapped as an optional; in our case
`option(move)`. Here is an example:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
const my_balance : option(move) =
moves [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)]
```
<!--CameLIGO-->
```cameligo
2020-01-28 18:13:50 +04:00
let my_balance : move option =
Big_map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
<!--ReasonLIGO-->
```reasonligo
let my_balance : option(move) =
Big_map.find_opt("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
```
<!--END_DOCUSAURUS_CODE_TABS-->
#### Obtaining a map value forcefully
2020-01-28 18:13:50 +04:00
Accessing a value in a map yields an option, however you can also get
the value directly:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
const my_balance : move =
get_force (("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), moves);
```
<!--CameLIGO-->
```cameligo
2020-01-28 18:13:50 +04:00
let my_balance : move =
Big_map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
<!--ReasonLIGO-->
```reasonligo
2020-01-28 18:13:50 +04:00
let my_balance : move =
Big_map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
```
<!--END_DOCUSAURUS_CODE_TABS-->
### Updating the contents of a big map
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
2020-01-28 18:13:50 +04:00
The values of a PascaLIGO big map can be updated using the ordinary
assignment syntax:
```pascaligo
function set_ (var m : moveset) : moveset is
block {
2020-01-28 18:13:50 +04:00
m [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)] := (4,9);
} with m
```
<!--Cameligo-->
2020-01-28 18:13:50 +04:00
We can update a big map in CameLIGO using the `Big_map.update`
built-in:
```cameligo
let updated_map : moveset =
Big_map.update ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) (Some (4,9)) moves
```
<!--Reasonligo-->
2020-01-28 18:13:50 +04:00
We can update a big map in ReasonLIGO using the `Big_map.update`
built-in:
```reasonligo
2020-01-28 18:13:50 +04:00
let updated_map : moveset =
Big_map.update(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), Some((4,9)), moves);
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Records
2020-01-28 18:13:50 +04:00
Records are a construct introduced in LIGO, and are not natively
available in Michelson. The LIGO compiler translates records into
Michelson `Pairs`.
2020-01-28 18:13:50 +04:00
Here is how a custom record type is defined:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
type user is
record
id : nat;
is_admin : bool;
name : string
end
```
<!--CameLIGO-->
```cameligo
type user = {
2020-01-28 18:13:50 +04:00
id : nat;
is_admin : bool;
name : string
}
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
type user = {
2020-01-28 18:13:50 +04:00
id : nat,
is_admin : bool,
name : string
2019-12-10 17:47:31 +04:00
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
2020-01-28 18:13:50 +04:00
And here is how a record value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
2020-01-28 18:13:50 +04:00
const user : user =
record
id = 1n;
is_admin = True;
2020-01-28 18:13:50 +04:00
name = "Alice"
end
```
<!--CameLIGO-->
```cameligo
2020-01-28 18:13:50 +04:00
let user : user = {
id = 1n;
is_admin = true;
2020-01-28 18:13:50 +04:00
name = "Alice"
}
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
2020-01-28 18:13:50 +04:00
let user : user = {
id : 1n,
is_admin : true,
name : "Alice"
2019-12-10 17:47:31 +04:00
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
### Accessing record keys by name
2020-01-28 18:13:50 +04:00
If we want to obtain a value from a record for a given key, we can do
the following:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const is_admin : bool = user.is_admin;
```
<!--CameLIGO-->
```cameligo
let is_admin : bool = user.is_admin
```
2019-12-11 13:34:08 +04:00
<!--ReasonLIGO-->
2019-12-10 17:47:31 +04:00
```reasonligo
let is_admin: bool = user.is_admin;
```
<!--END_DOCUSAURUS_CODE_TABS-->