Tuples, Lists, Sets
Apart from complex data types such as maps
and records
, LIGO also
features tuples
, lists
and sets
.
Tuples
Tuples gather a given number of values in a specific order and those
values, called components, can be retrieved by their index
(position). Probably the most common tuple is the pair. For
example, if we were storing coordinates on a two dimensional grid we
might use a pair (x,y)
to store the coordinates x
and y
. There
is a specific order, so (y,x)
is not equal to (x,y)
in
general. The number of components is part of the type of a tuple, so,
for example, we cannot add an extra component to a pair and obtain a
triple of the same type: (x,y)
has always a different type from
(x,y,z)
, whereas (y,x)
might have the same type as (x,y)
.
Tuples gather a given number of values in a specific order and those
values, called components, can be retrieved by their index
(position). Probably the most common tuple is the pair. For
example, if we were storing coordinates on a two dimensional grid we
might use a pair [x, y]
to store the coordinates x
and y
. There
is a specific order, so [y, x]
is not equal to [x, y]
in
general. The number of components is part of the type of a tuple, so,
for example, we cannot add an extra component to a pair and obtain a
triple of the same type: [x, y]
has always a different type from
[x, y, z]
, whereas [y, x]
might have the same type as [x, y]
.
Like records, tuple components can be of arbitrary types.
Defining Tuples
Unlike a record, tuple types do not have to be defined before they can be used. However below we will give them names by type aliasing.
type two_people = string * string // Alias
let friends : two_people = ("Alice", "Johnson") // Optional parentheses
type two_people = [string, string]; // Alias
const friends: two_people = ["Alice", "Johnson"];
Destructuring
If we want to get the first and second names of the two_people
type, we can use
destructuring. Destructuring a tuple allows you to give names to the elements
inside the tuple.
let (person_a, person_b) : two_people = friends
This also works in functions:
let first_person ((person_a, _): two_people) = person_a
let alice = first_person friends
Notice that we use the underscore to indicate that we ignore the last element of the tuple.
Destructuring
If we want to get the first and second names of the two_people
type, we can use
destructuring. Destructuring a tuple allows you to give names to the elements
inside the tuple.
let [person_a, person_b] = friends;
This also works in functions:
let first_person_fun = ([person_a, _person_b]: two_people) => person_a;
let alice = first_person_fun(friends);
note: the leading underscore to indicate that the argument
_person_b
is unused.
and within a code block:
let destruct_tuple = (x : [ int , [int , nat] ]) : nat => {
let [a,[b,c]] = x ;
return c
};
let destruct_record = (x : { a : int , b : string }) : int => {
let { a , b } = x ;
return a
};
note: nested patterns in record destructuring are not yet available
Accessing Components
Accessing the components of a tuple in OCaml is achieved by
pattern matching. LIGO
currently supports tuple patterns only in the parameters of functions,
not in pattern matching. However, we can access components by their
position in their tuple, which cannot be done in OCaml. Tuple
components are zero-indexed, that is, the first component has index
0
.
let first_name : string = friends.0
const first_name_component = friends[0];
Lists
Lists are linear collections of elements of the same type. Linear means that, in order to reach an element in a list, we must visit all the elements before (sequential access). Elements can be repeated, as only their order in the collection matters. The first element is called the head, and the sub-list after the head is called the tail. For those familiar with algorithmic data structure, you can think of a list a stack, where the top is written on the left.
💡 Lists are needed when returning operations from a smart contract's main function.
Defining Lists
let empty_list : int list = []
let my_list : int list = [1; 2; 2] (* The head is 1, the tail is [2; 2] *)
const empty_list: list<int> = list([]);
const my_list = list([1, 2, 2]); // The head is 1, the tail is list([2, 2])
Adding to Lists
Lists can be augmented by adding an element before the head (or, in terms of stack, by pushing an element on top). This operation is usually called consing in functional languages.
In CameLIGO, the cons operator is infix and noted ::
. It is not
symmetric: on the left lies the element to cons, and, on the right, a
list on which to cons.
let larger_list : int list = 5 :: my_list (* [5;1;2;2] *)
In JsLIGO, the cons operator is infix and noted , ...
. It is
not symmetric: on the left lies the element to cons, and, on the
right, a list on which to cons.
const larger_list = list([5, ...my_list]); // [5,1,2,2]