--- id: sets-lists-tuples title: Tuples, Lists, Sets --- import Syntax from '@theme/Syntax'; 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)`. Like records, tuple components can be of arbitrary types. ### Defining Tuples Unlike [a record](maps-records.md), tuple types do not have to be defined before they can be used. However below we will give them names by *type aliasing*. ```pascaligo group=tuple type full_name is string * string // Alias const full_name : full_name = ("Alice", "Johnson") ``` ```cameligo group=tuple type full_name = string * string // Alias let full_name : full_name = ("Alice", "Johnson") // Optional parentheses ``` ```reasonligo group=tuple type full_name = (string, string); // Alias let full_name : full_name = ("Alice", "Johnson"); ``` ### Destructuring If we want to get the first and last name of the `full_name` type, we can use destructuring. Destructuring a tuple allows you to give names to the elements inside the tuple. ```cameligo group=tuple let (first_name, last_name) : full_name = full_name ``` This also works in functions: ```cameligo group=tuple let first_name ((first_name, _): full_name) = first_name let alice = first_name full_name ``` 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 last name of the `full_name` type, we can use destructuring. Destructuring a tuple allows you to give names to the elements inside the tuple. ```reasonligo group=tuple let (first_name, last_name) : full_name = full_name ``` This also works in functions: ```reasonligo group=tuple let first_name = ((first_name, _): full_name) => first_name let alice = first_name(full_name) ``` Notice that we use the underscore to indicate that we ignore the last element of the tuple. ### Accessing Components Accessing the components of a tuple in OCaml is achieved by [pattern matching](unit-option-pattern-matching.md). 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`. ```pascaligo group=tuple const first_name : string = full_name.0 ``` ```cameligo group=tuple let first_name : string = full_name.0 ``` ```reasonligo group=tuple let first_name : string = full_name[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 ```pascaligo group=lists const empty_list : list (int) = nil // Or list [] const my_list : list (int) = list [1; 2; 2] // The head is 1 ``` ```cameligo group=lists let empty_list : int list = [] let my_list : int list = [1; 2; 2] // The head is 1 ``` ```reasonligo group=lists let empty_list : list (int) = []; let my_list : list (int) = [1, 2, 2]; // The head is 1 ``` ### 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 PascaLIGO, 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. (The symbol is helpfully asymmetric to remind you of that.) ```pascaligo group=lists const larger_list : list (int) = 5 # my_list // [5;1;2;2] ``` 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. ```cameligo group=lists let larger_list : int list = 5 :: my_list // [5;1;2;2] ``` In ReasonLIGO, 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. ```reasonligo group=lists let larger_list : list (int) = [5, ...my_list]; // [5,1,2,2] ``` ### Functional Iteration over Lists 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). There are three kinds of functional iterations over LIGO lists: the *iterated operation*, the *map operation* (not to be confused with the *map data structure*) and the *fold operation*. #### Iterated Operation over Lists The first, the *iterated operation*, is an iteration over the list with a unit return value. It is useful to enforce certain invariants on the element of a list, or fail. For example you might want to check that each value inside of a list is within a certain range, and fail otherwise. The predefined functional iterator implementing the iterated operation over lists is called `List.iter`. In the following example, a list is iterated to check that all its elements (integers) are strictly greater than `3`. ```pascaligo group=lists function iter_op (const l : list (int)) : unit is block { function iterated (const i : int) : unit is if i > 3 then Unit else (failwith ("Below range.") : unit) } with List.iter (iterated, l) ``` > Note that `list_iter` is *deprecated*. ```cameligo group=lists let iter_op (l : int list) : unit = let predicate = fun (i : int) -> assert (i > 3) in List.iter predicate l ``` ```reasonligo group=lists let iter_op = (l : list (int)) : unit => { let predicate = (i : int) => assert (i > 3); List.iter (predicate, l); }; ``` #### Mapped Operation over Lists We may want to change all the elements of a given list 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 mapped operation over lists is called `List.map` and is used as follows. ```pascaligo group=lists function increment (const i : int): int is i + 1 // Creates a new list with all elements incremented by 1 const plus_one : list (int) = List.map (increment, larger_list) ``` > Note that `list_map` is *deprecated*. ```cameligo group=lists let increment (i : int) : int = i + 1 // Creates a new list with all elements incremented by 1 let plus_one : int list = List.map increment larger_list ``` ```reasonligo group=lists let increment = (i : int) : int => i + 1; // Creates a new list with all elements incremented by 1 let plus_one : list (int) = List.map (increment, larger_list); ``` #### Folded Operation over Lists 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 lists is called `List.fold` and is used as follows. ```pascaligo group=lists function sum (const acc : int; const i : int): int is acc + i const sum_of_elements : int = List.fold (sum, my_list, 0) ``` > Note that `list_fold` is *deprecated*. ```cameligo group=lists let sum (acc, i: int * int) : int = acc + i let sum_of_elements : int = List.fold sum my_list 0 ``` ```reasonligo group=lists let sum = ((result, i): (int, int)): int => result + i; let sum_of_elements : int = List.fold (sum, my_list, 0); ``` ## Sets Sets are unordered collections of values of the same type, like lists are ordered collections. Like the mathematical sets and lists, sets can be empty and, if not, elements of sets in LIGO are *unique*, whereas they can be repeated in a *list*. ### Empty Sets In PascaLIGO, the notation for sets is similar to that for lists, except the keyword `set` is used before: ```pascaligo group=sets const my_set : set (int) = set [] ``` In CameLIGO, the empty set is denoted by the predefined value `Set.empty`. ```cameligo group=sets let my_set : int set = Set.empty ``` In ReasonLIGO, the empty set is denoted by the predefined value `Set.empty`. ```reasonligo group=sets let my_set : set (int) = Set.empty; ``` ### Non-empty Sets In PascaLIGO, the notation for sets is similar to that for lists, except the keyword `set` is used before: ```pascaligo group=sets const my_set : set (int) = set [3; 2; 2; 1] ``` You can check that `2` is not repeated in `my_set` by using the LIGO compiler like this (the output will sort the elements of the set, but that order is not significant for the compiler): ```shell ligo evaluate-value gitlab-pages/docs/language-basics/src/sets-lists-tuples/sets.ligo my_set # Outputs: { 3 ; 2 ; 1 } ``` In CameLIGO, there is no predefined syntactic construct for sets: you must build your set by adding to the empty set. (This is the way in OCaml.) ```cameligo group=sets let my_set : int set = Set.add 3 (Set.add 2 (Set.add 2 (Set.add 1 (Set.empty : int set)))) ``` You can check that `2` is not repeated in `my_set` by using the LIGO compiler like this (the output will sort the elements of the set, but that order is not significant for the compiler): ```shell ligo evaluate-value gitlab-pages/docs/language-basics/src/sets-lists-tuples/sets.mligo my_set # Outputs: { 3 ; 2 ; 1 } ``` In ReasonLIGO, there is no predefined syntactic construct for sets: you must build your set by adding to the empty set. (This is the way in OCaml.) ```reasonligo group=sets let my_set : set (int) = Set.add (3, Set.add (2, Set.add (2, Set.add (1, Set.empty : set (int))))); ``` You can check that `2` is not repeated in `my_set` by using the LIGO compiler like this (the output will sort the elements of the set, but that order is not significant for the compiler): ```shell ligo evaluate-value gitlab-pages/docs/language-basics/src/sets-lists-tuples/sets.religo my_set # Outputs: { 3 ; 2 ; 1 } ``` ### Set Membership PascaLIGO features a special keyword `contains` that operates like an infix operator checking membership in a set. ```pascaligo group=sets const contains_3 : bool = my_set contains 3 ``` In CameLIGO, the predefined predicate `Set.mem` tests for membership in a set as follows: ```cameligo group=sets let contains_3 : bool = Set.mem 3 my_set ``` In ReasonLIGO, the predefined predicate `Set.mem` tests for membership in a set as follows: ```reasonligo group=sets let contains_3 : bool = Set.mem (3, my_set); ``` ### Cardinal of Sets The predefined function `Set.size` returns the number of elements in a given set as follows. ```pascaligo group=sets const cardinal : nat = Set.size (my_set) ``` > Note that `size` is *deprecated*. ```cameligo group=sets let cardinal : nat = Set.size my_set ``` ```reasonligo group=sets let cardinal : nat = Set.size (my_set); ``` ### Updating Sets There are two ways to update a set, that is to add or remove from it. In PascaLIGO, either we create a new set from the given one, or we modify it in-place. First, let us consider the former way: ```pascaligo group=sets const larger_set : set (int) = Set.add (4, my_set) const smaller_set : set (int) = Set.remove (3, my_set) ``` > Note that `set_add` and `set_remove` are *deprecated*. If we are in a block, we can use an instruction to modify the set bound to a given variable. This is called a *patch*. It is only possible to add elements by means of a patch, not remove any: it is the union of two sets. In the following example, the parameter set `s` of function `update` is augmented (as the `with s` shows) to include `4` and `7`, that is, this instruction is equivalent to perform the union of two sets, one that is modified in-place, and the other given as a literal (extensional definition). ```pascaligo group=sets function update (var s : set (int)) : set (int) is block { patch s with set [4; 7] } with s const new_set : set (int) = update (my_set) ``` In CameLIGO, we can use the predefined functions `Set.add` and `Set.remove`. We update a given set by creating another one, with or without some elements. ```cameligo group=sets let larger_set : int set = Set.add 4 my_set let smaller_set : int set = Set.remove 3 my_set ``` In ReasonLIGO, we can use the predefined functions `Set.add` and `Set.remove`. We update a given set by creating another one, with or without some elements. ```reasonligo group=sets let larger_set : set (int) = Set.add (4, my_set); let smaller_set : set (int) = Set.remove (3, my_set); ``` ### Functional Iteration over Sets 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). There are three kinds of functional iterations over LIGO maps: the *iterated operation*, the *mapped operation* (not to be confused with the *map data structure*) and the *folded operation*. #### Iterated Operation 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 sets is called `Set.iter`. In the following example, a set is iterated to check that all its elements (integers) are greater than `3`. ```pascaligo group=sets function iter_op (const s : set (int)) : unit is block { function iterated (const i : int) : unit is if i > 2 then Unit else (failwith ("Below range.") : unit) } with Set.iter (iterated, s) ``` > Note that `set_iter` is *deprecated*. ```cameligo group=sets let iter_op (s : int set) : unit = let predicate = fun (i : int) -> assert (i > 3) in Set.iter predicate s ``` ```reasonligo group=sets let iter_op = (s : set (int)) : unit => { let predicate = (i : int) => assert (i > 3); Set.iter (predicate, s); }; ``` #### Folded Operation 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 fold over sets is called `Set.fold`. ```pascaligo group=sets function sum (const acc : int; const i : int): int is acc + i const sum_of_elements : int = Set.fold (sum, my_set, 0) ``` > Note that `set_fold` is *deprecated*. It is possible to use a *loop* over a set as well. ```pascaligo group=sets function loop (const s : set (int)) : int is block { var sum : int := 0; for element in set s block { sum := sum + element } } with sum ``` ```cameligo group=sets let sum (acc, i : int * int) : int = acc + i let sum_of_elements : int = Set.fold sum my_set 0 ``` ```reasonligo group=sets let sum = ((acc, i) : (int, int)) : int => acc + i; let sum_of_elements : int = Set.fold (sum, my_set, 0); ```