2019-11-08 03:19:27 +04:00
|
|
|
---
|
|
|
|
id: maps-records
|
2020-02-05 19:28:40 +04:00
|
|
|
title: Records and Maps
|
2019-11-08 03:19:27 +04:00
|
|
|
---
|
|
|
|
|
2020-01-28 18:13:50 +04:00
|
|
|
So far we have seen pretty basic data types. LIGO also offers more
|
2020-02-05 19:28:40 +04:00
|
|
|
complex built-in constructs, such as *records* and *maps*.
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
## Records
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Records are one way data of different types can be packed into a
|
|
|
|
single type. A record is made of a set of *fields*, which are made of
|
|
|
|
a *field name* and a *field type*. Given a value of a record type, the
|
|
|
|
value bound to a field can be accessed by giving its field name to a
|
|
|
|
special operator (`.`).
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Let us first consider and example of record type declaration.
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=a
|
|
|
|
type user is
|
|
|
|
record [
|
|
|
|
id : nat;
|
|
|
|
is_admin : bool;
|
|
|
|
name : string
|
|
|
|
]
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=a
|
|
|
|
type user = {
|
|
|
|
id : nat;
|
|
|
|
is_admin : bool;
|
|
|
|
name : string
|
|
|
|
}
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=a
|
|
|
|
type user = {
|
|
|
|
id : nat,
|
|
|
|
is_admin : bool,
|
|
|
|
name : string
|
|
|
|
};
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
And here is how a record value is defined:
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=a
|
|
|
|
const alice : user =
|
|
|
|
record [
|
|
|
|
id = 1n;
|
|
|
|
is_admin = True;
|
|
|
|
name = "Alice"
|
2019-11-08 03:19:27 +04:00
|
|
|
]
|
|
|
|
```
|
2019-12-10 17:47:31 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=a
|
|
|
|
let alice : user = {
|
|
|
|
id = 1n;
|
|
|
|
is_admin = true;
|
|
|
|
name = "Alice"
|
|
|
|
}
|
2019-12-11 13:34:08 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=a
|
|
|
|
let alice : user = {
|
|
|
|
id : 1n,
|
|
|
|
is_admin : true,
|
|
|
|
name : "Alice"
|
|
|
|
};
|
|
|
|
```
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Accessing Record Fields
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
If we want the contents of a given field, we use the `.` infix
|
|
|
|
operator, like so:
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=a
|
|
|
|
const alice_admin : bool = alice.is_admin
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=a
|
|
|
|
let alice_admin : bool = alice.is_admin
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
2019-12-10 17:47:31 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=a
|
|
|
|
let alice_admin: bool = alice.is_admin;
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Functional Updates
|
|
|
|
|
|
|
|
Given a record value, it is a common design pattern to update only a
|
|
|
|
small number of its fields. Instead of copying the fields that are
|
|
|
|
unchanged, LIGO offers a way to only update the fields that are
|
|
|
|
modified.
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
One way to understand the update of record values is the *functional
|
|
|
|
update*. The idea is to have an *expression* whose value is the
|
|
|
|
updated record. The shape of that expression is `<record variable>
|
|
|
|
with <record value>`. The record variable is the record to update and
|
|
|
|
the record value is the update itself.
|
|
|
|
|
|
|
|
Let us consider defining a function that translates three-dimensional
|
|
|
|
points on a plane.
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=b
|
|
|
|
type point is record [x : int; y : int; z : int]
|
|
|
|
type vector is record [dx : int; dy : int]
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
const origin : point = record [x = 0; y = 0; z = 0]
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
function xy_translate (var p : point; const vec : vector) : point is
|
|
|
|
p with record [x = p.x + vec.dx; y = p.y + vec.dy]
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
2019-11-26 16:08:16 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the function `xy_translate` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_update.ligo
|
|
|
|
translate "(record [x=2;y=3;z=1], record [dx=3;dy=4])"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You have to understand that `p` has not been changed by the functional
|
|
|
|
update: a namless new version of it has been created and returned by
|
|
|
|
the blockless function.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
The syntax for the functional updates of record in CameLIGO follows
|
|
|
|
that of OCaml:
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```cameligo group=b
|
|
|
|
type point = {x : int; y : int; z : int}
|
|
|
|
type vector = {dx : int; dy : int}
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
let origin : point = {x = 0; y = 0; z = 0}
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
let xy_translate (p, vec : point * vector) : point =
|
|
|
|
{p with x = p.x + vec.dx; y = p.y + vec.dy}
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the function `xy_translate` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_update.mligo
|
|
|
|
xy_translate "({x=2;y=3;z=1}, {dx=3;dy=4})"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
|
|
|
```
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> You have to understand that `p` has not been changed by the
|
|
|
|
> functional update: a nameless new version of it has been created and
|
|
|
|
> returned.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
The syntax for the functional updates of record in ReasonLIGO follows
|
|
|
|
that of OCaml:
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```reasonligo group=b
|
|
|
|
type point = {x : int, y : int, z : int};
|
|
|
|
type vector = {dx : int, dy : int};
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
let origin : point = {x : 0, y : 0, z : 0};
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
let xy_translate = ((p, vec) : (point, vector)) : point =>
|
|
|
|
{...p, x : p.x + vec.dx, y : p.y + vec.dy};
|
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the function `x_translation` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_update.religo
|
|
|
|
xy_translate "({x:2,y:3,z:1}, {dx:3,dy:4})"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You have to understand that `p` has not been changed by the functional
|
|
|
|
update: a nameless new version of it has been created and returned.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Imperative Updates
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Another way to understand what it means to update a record value is to
|
|
|
|
make sure that any further reference to the value afterwards will
|
|
|
|
exhibit the modification. This is called a `patch` and this is only
|
|
|
|
possible in PascaLIGO, because a patch is an *instruction*, therefore
|
|
|
|
we can only use it in a block. Similarly to a *functional update*, a
|
|
|
|
patch takes a record to be updated and a record with a subset of the
|
|
|
|
fields to update, then applies the latter to the former (hence the
|
|
|
|
name "patch").
|
2019-11-26 16:08:16 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Let us consider defining a function that translates three-dimensional
|
|
|
|
points on a plane.
|
2019-11-26 16:08:16 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
2019-11-26 16:08:16 +04:00
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=c
|
|
|
|
type point is record [x : int; y : int; z : int]
|
|
|
|
type vector is record [dx : int; dy : int]
|
|
|
|
|
|
|
|
const origin : point = record [x = 0; y = 0; z = 0]
|
|
|
|
|
|
|
|
function xy_translate (var p : point; const vec : vector) : point is
|
2019-11-26 16:08:16 +04:00
|
|
|
block {
|
2020-02-05 19:28:40 +04:00
|
|
|
patch p with record [x = p.x + vec.dx];
|
|
|
|
patch p with record [y = p.y + vec.dy]
|
|
|
|
} with p
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the function `xy_translate` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_patch.ligo
|
|
|
|
xy_translate "(record [x=2;y=3;z=1], record [dx=3;dy=4])"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
2019-12-10 17:47:31 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Of course, we can actually translate the point with only one `patch`,
|
|
|
|
as the previous example was meant to show that, after the first patch,
|
|
|
|
the value of `p` indeed changed. So, a shorter version would be
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=d
|
|
|
|
type point is record [x : int; y : int; z : int]
|
|
|
|
type vector is record [dx : int; dy : int]
|
2019-11-26 16:08:16 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
const origin : point = record [x = 0; y = 0; z = 0]
|
|
|
|
|
|
|
|
function xy_translate (var p : point; const vec : vector) : point is
|
2019-11-26 16:08:16 +04:00
|
|
|
block {
|
2020-02-05 19:28:40 +04:00
|
|
|
patch p with record [x = p.x + vec.dx; y = p.y + vec.dy]
|
|
|
|
} with p
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the new function `xy_translate` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_patch2.ligo
|
|
|
|
xy_translate "(record [x=2;y=3;z=1], record [dx=3;dy=4])"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
2019-12-10 17:47:31 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Record patches can actually be simulated with functional updates. All
|
|
|
|
we have to do is *declare a new record value with the same name as the
|
|
|
|
one we want to update* and use a functional update, like so:
|
2019-11-26 16:08:16 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=e
|
|
|
|
type point is record [x : int; y : int; z : int]
|
|
|
|
type vector is record [dx : int; dy : int]
|
2019-11-26 16:08:16 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
const origin : point = record [x = 0; y = 0; z = 0]
|
2019-11-26 17:39:54 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
function xy_translate (var p : point; const vec : vector) : point is block {
|
|
|
|
const p : point = p with record [x = p.x + vec.dx; y = p.y + vec.dy]
|
|
|
|
} with p
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
You can call the new function `xy_translate` defined above by running the
|
|
|
|
following command of the shell:
|
|
|
|
```shell
|
|
|
|
ligo run-function
|
|
|
|
gitlab-pages/docs/language-basics/src/maps-records/record_simu.ligo
|
|
|
|
xy_translate "(record [x=2;y=3;z=1], record [dx=3;dy=4])"
|
|
|
|
# Outputs: {z = 1 , y = 7 , x = 5}
|
2019-11-26 16:08:16 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
The hiding of a variable by another (here `p`) is called `shadowing`.
|
2019-12-10 17:47:31 +04:00
|
|
|
|
2019-11-26 16:08:16 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
## Maps
|
|
|
|
|
|
|
|
*Maps* are a data structure which associate values of the same type to
|
|
|
|
values of the same type. The former are called *key* and the latter
|
|
|
|
*values*. Together they make up a *binding*. An additional requirement
|
|
|
|
is that the type of the keys must be *comparable*, in the Michelson
|
|
|
|
sense.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Here is how a custom map from addresses to a pair of integers is
|
|
|
|
defined.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=f
|
|
|
|
type move is int * int
|
|
|
|
type register is map (address, move)
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
2020-01-07 11:32:44 +04:00
|
|
|
type move = int * int
|
2020-02-05 19:28:40 +04:00
|
|
|
type register = (address, move) map
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
2020-01-07 11:32:44 +04:00
|
|
|
type move = (int, int);
|
2020-02-05 19:28:40 +04:00
|
|
|
type register = map (address, move);
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
And here is how a map value is defined:
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=f
|
|
|
|
const moves : register =
|
|
|
|
map [
|
|
|
|
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address) -> (1,2);
|
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) -> (0,3)]
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> Notice the `->` between the key and its value and `;` to separate
|
|
|
|
> individual map entries. The annotated value `("<string value>" :
|
|
|
|
> address)` means that we cast a string into an address. Also, `map`
|
|
|
|
> is a keyword.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
|
|
|
let moves : register =
|
|
|
|
Map.literal [
|
|
|
|
(("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address), (1,2));
|
|
|
|
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address), (0,3))]
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> The `Map.literal` predefined function builds a 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.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
|
|
|
let moves : register =
|
|
|
|
Map.literal ([
|
|
|
|
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address, (1,2)),
|
2020-02-06 14:47:41 +04:00
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address, (0,3))]);
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
> The `Map.literal` predefined function builds a 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.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Accessing Map Bindings
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
We can use the postfix `[]` operator to read the `move` value
|
|
|
|
associated to a given key (`address` here) in the register. Here is an
|
|
|
|
example:
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=f
|
2020-02-06 14:47:41 +04:00
|
|
|
const my_balance : option (move) =
|
|
|
|
moves [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address)]
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
2020-01-28 18:13:50 +04:00
|
|
|
let my_balance : move option =
|
2020-02-05 19:28:40 +04:00
|
|
|
Map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) moves
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
|
|
|
let my_balance : option (move) =
|
|
|
|
Map.find_opt (("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address), moves);
|
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
Notice how the value we read is an optional value: this is to force
|
|
|
|
the reader to account for a missing key in the map. This requires
|
|
|
|
*pattern matching*.
|
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
<!--Pascaligo-->
|
|
|
|
```pascaligo group=f
|
|
|
|
function force_access (const key : address; const moves : register) : move is
|
|
|
|
case moves[key] of
|
|
|
|
Some (move) -> move
|
|
|
|
| None -> (failwith ("No move.") : move)
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
|
|
|
let force_access (key, moves : address * register) : move =
|
|
|
|
match Map.find_opt key moves with
|
|
|
|
Some move -> move
|
|
|
|
| None -> (failwith "No move." : move)
|
|
|
|
```
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
2020-02-06 14:47:41 +04:00
|
|
|
```reasonligo group=f
|
|
|
|
let force_access = ((key, moves) : (address, register)) : move => {
|
|
|
|
switch (Map.find_opt (key, moves)) {
|
|
|
|
| Some (move) => move
|
|
|
|
| None => failwith ("No move.") : move
|
|
|
|
}
|
2020-02-05 19:28:40 +04:00
|
|
|
};
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Updating a Map
|
|
|
|
|
|
|
|
Given a map, we may want to add a new binding, remove one, or modify
|
|
|
|
one by changing the value associated to an already existing key. We
|
|
|
|
may even want to retain the key but not the associated value. All
|
|
|
|
those operations are called *updates*.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
2020-01-07 11:32:44 +04:00
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
The values of a PascaLIGO map can be updated using the usual
|
|
|
|
assignment syntax `<map variable>[<key>] := <new value>`. Let us
|
|
|
|
consider an example.
|
|
|
|
|
|
|
|
```pascaligo group=f
|
|
|
|
function assign (var m : register) : register is
|
|
|
|
block {
|
|
|
|
m [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)] := (4,9)
|
|
|
|
} with m
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
If multiple bindings need to be updated, PascaLIGO offers a *patch
|
|
|
|
instruction* for maps, similar to that for records.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=f
|
|
|
|
function assignments (var m : register) : register is
|
|
|
|
block {
|
|
|
|
patch m with map [
|
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) -> (4,9);
|
|
|
|
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address) -> (1,2)
|
|
|
|
]
|
|
|
|
} with m
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
|
|
|
|
We can update a binding in a map in CameLIGO by means of the
|
|
|
|
`Map.update` built-in function:
|
|
|
|
|
|
|
|
```cameligo group=f
|
|
|
|
let assign (m : register) : register =
|
|
|
|
Map.update
|
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) (Some (4,9)) m
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> Notice the optional value `Some (4,9)` instead of `(4,9)`. If we had
|
2020-02-06 14:47:41 +04:00
|
|
|
> use `None` instead, that would have meant that the binding is
|
|
|
|
> removed.
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
<!--Reasonligo-->
|
|
|
|
|
|
|
|
We can update a binding in a map in ReasonLIGO by means of the
|
|
|
|
`Map.update` built-in function:
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
```reasonligo group=f
|
|
|
|
let assign = (m : register) : register => {
|
|
|
|
Map.update
|
|
|
|
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address), Some ((4,9)), m)
|
|
|
|
};
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> Notice the optional value `Some (4,9)` instead of `(4,9)`. If we had
|
2020-02-06 14:47:41 +04:00
|
|
|
> use `None` instead, that would have meant that the binding is
|
|
|
|
> removed.
|
2020-02-05 19:28:40 +04:00
|
|
|
|
2020-01-07 11:32:44 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
To remove a binding from a map, we need its key.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
<!--Pascaligo-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
In PascaLIGO, there is a special instruction to remove a binding from
|
|
|
|
a map.
|
|
|
|
```pascaligo group=f
|
|
|
|
function delete (const key : address; var moves : register) : register is
|
|
|
|
block {
|
|
|
|
remove key from map moves
|
|
|
|
} with moves
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
|
|
|
let delete (key, moves : address * register) : register =
|
|
|
|
Map.remove key moves
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
|
|
|
let delete = ((key, moves) : (address, register)) : register => {
|
|
|
|
Map.remove (key, moves);
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
|
|
|
|
### Iterating Functionally over a Map
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
A *functional iterator* is a function that traverses a data structure
|
|
|
|
and calls in turn a given function over the elements of that structure
|
|
|
|
to compute some value. Another approach is possible in PascaLIGO:
|
|
|
|
*loops* (see the relevant section).
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
There are three kinds of functional iteration over LIGO maps: `iter`,
|
|
|
|
`map` and `fold`. The first, `iter`, is an iteration over the map with
|
|
|
|
no return value: its only use is to produce 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, and fail with an error otherwise.
|
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
|
|
|
```pascaligo group=f
|
|
|
|
function iter_op (const m : register) : unit is
|
2020-01-08 20:16:09 +04:00
|
|
|
block {
|
2020-02-05 19:28:40 +04:00
|
|
|
function aggregate (const i : address; const j : move) : unit is block
|
|
|
|
{ if j.1 > 1 then skip else failwith ("Below range.") } with unit
|
|
|
|
} with map_iter (aggregate, m)
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```cameligo group=f
|
|
|
|
let iter_op (m : register) : unit =
|
|
|
|
let assert_eq = fun (i,j : address * move) -> assert (j.0 > 1)
|
|
|
|
in Map.iter assert_eq m
|
|
|
|
```
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
|
|
|
let iter_op = (m : register) : unit => {
|
|
|
|
let assert_eq = ((i,j) : (address, move)) => assert (j[0] > 1);
|
|
|
|
Map.iter (assert_eq, m);
|
|
|
|
};
|
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
|
|
|
We may want to change all the bindings of a map by applying to them a
|
|
|
|
function. This is also called a *map operation*, as opposed to the
|
|
|
|
*map data structure* we have been presenting.
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
|
|
|
```pascaligo group=f
|
|
|
|
function map_op (const m : register) : register is
|
|
|
|
block {
|
|
|
|
function increment (const i : address; const j : move) : move is
|
|
|
|
(j.0, j.1 + 1);
|
|
|
|
} with map_map (increment, m)
|
|
|
|
```
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
|
|
|
let map_op (m : register) : register =
|
|
|
|
let increment = fun (i,j : address * move) -> j.0, j.1 + 1
|
|
|
|
in Map.map increment m
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
<!--Reasonligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```reasonligo group=f
|
|
|
|
let map_op = (m : register) : register => {
|
|
|
|
let increment = ((i,j): (address, move)) => (j[0], j[1] + 1);
|
|
|
|
Map.map(increment, m);
|
|
|
|
};
|
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
A *fold operation* is the most general of iterations. The iterated
|
|
|
|
function takes two arguments: an *accumulator* and the structure
|
|
|
|
*element* at hand, with which it then produces a new accumulator. This
|
2020-02-06 14:47:41 +04:00
|
|
|
enables having a partial result that becomes complete when the
|
2020-02-05 19:28:40 +04:00
|
|
|
traversal of the data structure is over.
|
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
|
|
|
```pascaligo group=f
|
|
|
|
function fold_op (const m : register) : int is block {
|
|
|
|
function aggregate (const j : int; const cur : address * move) : int is
|
|
|
|
j + cur.1.1
|
|
|
|
} with map_fold (aggregate, m, 5)
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=f
|
|
|
|
let fold_op (m : register) : register =
|
|
|
|
let aggregate = fun (i,j : int * (address * move)) -> i + j.1.1
|
|
|
|
in Map.fold aggregate m 5
|
|
|
|
```
|
2020-01-07 11:32:44 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=f
|
2020-02-06 14:47:41 +04:00
|
|
|
let fold_op = (m : register) : register => {
|
2020-02-05 19:28:40 +04:00
|
|
|
let aggregate = ((i,j): (int, (address, move))) => i + j[1][1];
|
|
|
|
Map.fold (aggregate, m, 5);
|
|
|
|
};
|
2020-01-07 11:32:44 +04:00
|
|
|
```
|
|
|
|
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
## Big Maps
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
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 holding *many* entries, potentially millions of
|
|
|
|
them. The cost of loading those 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
|
|
|
|
Michelson which handles the scaling concerns for us. In LIGO, the
|
|
|
|
interface for big maps is analogous to the one used for ordinary maps.
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
Here is how we define a big map:
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
```pascaligo group=g
|
|
|
|
type move is int * int
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
type register is big_map (address, move)
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
```cameligo group=g
|
|
|
|
type move = int * int
|
|
|
|
|
|
|
|
type register = (address, move) big_map
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
```reasonligo group=g
|
|
|
|
type move = (int, int);
|
|
|
|
|
|
|
|
type register = big_map(address, move);
|
|
|
|
```
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
And here is how a map value is created:
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
```pascaligo group=g
|
|
|
|
const moves : register =
|
|
|
|
big_map [
|
|
|
|
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address) -> (1,2);
|
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) -> (0,3)]
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> Notice the right arrow `->` between the key and its value and the
|
|
|
|
> semicolon separating individual map entries. The value annotation
|
|
|
|
> `("<string value>" : address)` means that we cast a string into an
|
|
|
|
> address.
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
|
|
|
|
```cameligo group=g
|
|
|
|
let moves : register =
|
|
|
|
Big_map.literal [
|
|
|
|
(("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address), (1,2));
|
|
|
|
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address), (0,3))]
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
> The predefind function `Big_map.literal` constructs a big map from a
|
|
|
|
> list of key-value pairs `(<key>, <value>)`. Note also the semicolon
|
|
|
|
> separating individual map entries. The annotated value `("<string
|
|
|
|
> value>" : address)` means that we cast a string into an address.
|
|
|
|
|
|
|
|
<!--Reasonligo-->
|
|
|
|
|
|
|
|
```reasonligo group=g
|
|
|
|
let moves : register =
|
|
|
|
Big_map.literal ([
|
|
|
|
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address, (1,2)),
|
2020-02-06 14:47:41 +04:00
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address, (0,3))]);
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
> The predefind function `Big_map.literal` constructs a big map from a
|
|
|
|
> list of key-value pairs `(<key>, <value>)`. Note also the semicolon
|
|
|
|
> separating individual map entries. The annotated value `("<string
|
|
|
|
> value>" : address)` means that we cast a string into an address.
|
|
|
|
|
|
|
|
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Accessing Values by Key
|
|
|
|
|
|
|
|
If we want to access a move from our `register` above, we can use the
|
|
|
|
postfix `[]` operator to read the associated `move` value. However,
|
|
|
|
the value we read is an optional value: in our case, of type `option
|
|
|
|
(move)`. Here is an example:
|
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
|
|
|
<!--Pascaligo-->
|
|
|
|
```pascaligo group=g
|
|
|
|
const my_balance : option (move) =
|
|
|
|
moves [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address)]
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Cameligo-->
|
|
|
|
|
|
|
|
```cameligo group=g
|
|
|
|
let my_balance : move option =
|
|
|
|
Big_map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) moves
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--Reasonligo-->
|
|
|
|
|
|
|
|
```reasonligo group=g
|
|
|
|
let my_balance : option (move) =
|
|
|
|
Big_map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address, moves);
|
|
|
|
```
|
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
2019-11-08 03:19:27 +04:00
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
### Updating a Big Map
|
2019-11-08 03:19:27 +04:00
|
|
|
|
|
|
|
<!--DOCUSAURUS_CODE_TABS-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--Pascaligo-->
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
The values of a PascaLIGO big map can be updated using the
|
|
|
|
assignment syntax for ordinary maps
|
|
|
|
|
|
|
|
```pascaligo group=g
|
|
|
|
function assign (var m : register) : register is
|
|
|
|
block {
|
|
|
|
m [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)] := (4,9)
|
|
|
|
} with m
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Cameligo-->
|
|
|
|
|
|
|
|
We can update a big map in CameLIGO using the `Big_map.update`
|
|
|
|
built-in:
|
|
|
|
|
|
|
|
```cameligo group=g
|
|
|
|
let updated_map : register =
|
|
|
|
Big_map.update
|
|
|
|
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" : address) (Some (4,9)) moves
|
2019-11-08 03:19:27 +04:00
|
|
|
```
|
|
|
|
|
2020-02-05 19:28:40 +04:00
|
|
|
<!--Reasonligo-->
|
|
|
|
|
|
|
|
We can update a big map in ReasonLIGO using the `Big_map.update`
|
|
|
|
built-in:
|
|
|
|
|
|
|
|
```reasonligo group=g
|
|
|
|
let updated_map : register =
|
|
|
|
Big_map.update
|
|
|
|
(("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), Some((4,9)), moves);
|
2019-12-10 17:47:31 +04:00
|
|
|
```
|
2020-02-05 19:28:40 +04:00
|
|
|
|
|
|
|
### Removing Bindings from a Map
|
|
|
|
|
2019-11-08 03:19:27 +04:00
|
|
|
<!--END_DOCUSAURUS_CODE_TABS-->
|