Records/Objects and Maps
So far, we have seen pretty basic data types. LIGO also offers more complex built-in constructs, such as records and maps.
Records/Objects
Objects are one-way data of different types can be packed into a
single type. An object is made of a set of properties, which are
made of a property name and a property 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 (.
).
Let us first consider an example of object type declaration.
And here is how an object value is defined:
Accessing Record Fields
If we want the contents of a given field, we use the (.
) infix
operator, like so:
Destructuring Records
We can also access fields of a record using the destructuring syntax. This allows accessing multiple fields of a record in a concise manner, like so:
We can ignore some fields of the records we can do so by
using _
(underscore), like so:
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.
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.
Let us consider defining a function that translates three-dimensional points on a plane.
The syntax for the functional updates of record in JsLIGO:
You can call the function xy_translate
defined above by running the
following command of the shell:
It is important to understand that
p
has not been changed by the functional update: a nameless new version of it has been created and returned.
Nested updates
A unique feature of LIGO is the ability to perform nested updates on records. JsLIGO however does not support the specialised syntax as the other syntaxes. The following however also does the trick.
For example if you have the following record structure:
You can update the nested record with the following code:
Note that all the records in the path will get updated. In this
example, those are account
and preferences
.
You can call the function change_color_preference
defined above by running the
following command:
Comparison
Record types are comparable, which allows to check for equality and
use records as key in sets or maps. By default, the ordering of
records is undefined and implementation-dependent. Ultimately, the
order is determined by the translated Michelson type. When using the
decorator @layout("comb")
, fields are translated in their order in
the record, and objects are then ordered with lexicographic ordering.
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.
Declaring a Map
Here is how a custom map from addresses to a pair of integers is defined.
Creating an Empty Map
Here is how to create an empty map.
Creating a Non-empty Map
And here is how to create a non-empty map value:
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>" as address
means
that we type-cast a string into an address.
Accessing Map Bindings
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.
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. All those operations are called updates.
We can update a binding in a map in JsLIGO by means of the
Map.update
built-in function:
Notice the optional value Some ([4,9])
instead of [4, 9]
. If we used
None
instead that would have meant that the binding is removed.
As a particular case, we can only add a key and its associated value.
To remove a binding from a map, we need its key.
In JsLIGO, we use the predefined function Map.remove
as follows:
Functional Iteration over Maps
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 JsLIGO: loops (see the relevant section).
There are three kinds of functional iterations over LIGO maps: the iterated operation, the map operation (not to be confused with the map data structure) and the fold operation.
Iterated Operation over Maps
The first, the iterated operation, 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.
The predefined functional iterator implementing the iterated operation
over maps is called Map.iter
. In the following example, the register
of moves is iterated to check that the start of each move is above
3
.
Map Operations over Maps
We may want to change all the bindings of a map by applying to them a
function. This is called a map operation, not to be confused with
the map data structure. The predefined functional iterator
implementing the map operation over maps is called Map.map
. In the
following example, we add 1
to the ordinate of the moves in the
register.
Folded Operations over Maps
A folded operation is the most general of iterations. The folded function takes two arguments: an accumulator and the structure element at hand, with which it then produces a new accumulator. This enables having a partial result that becomes complete when the traversal of the data structure is over.
The predefined functional iterator implementing the folded operation
over maps is called Map.fold
and is used as follows.
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 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.
Declaring a Map
Here is how we define a big map:
Creating an Empty Big Map
Here is how to create an empty big map.
Creating a Non-empty Map
And here is how to create a non-empty map value:
The predefined 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>" as address)
means that we cast a string into an address.
Accessing Values
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)
), to account for a missing key. Here is an example:
Updating Big Maps
We can update a big map in JsLIGO using the Big_map.update
built-in:
Removing Bindings
Removing a binding in a map is done differently according to the LIGO syntax.
In JsLIGO, the predefined function which removes a binding in a map
is called Map.remove
and is used as follows: