Merge branch 'feature/doc-pascaligo-loop' into 'dev'

Some doc update for Ligo training

See merge request ligolang/ligo!364
This commit is contained in:
Christian Rinderknecht 2020-01-29 14:56:50 +00:00
commit 04381b9dcf
4 changed files with 265 additions and 165 deletions

View File

@ -5,63 +5,132 @@ title: Entrypoints, Contracts
## Entrypoints
Each LIGO smart contract is essentially a single function, that has the following *(pseudo)* type signature:
Each LIGO smart contract is essentially a single main function, referring to the following types:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```
(const parameter: my_type, const store: my_store_type): (list(operation), my_store_type)
```pascaligo group=a
type parameter_t is unit
type storage_t is unit
type return_t is (list(operation) * storage_t)
```
<!--CameLIGO-->
```
(parameter, store: my_type * my_store_type) : operation list * my_store_type
```cameligo group=a
type parameter_t = unit
type storage_t = unit
type return_t = (operation list * storage_t)
```
<!--ReasonLIGO-->
```reasonligo group=a
type parameter_t = unit;
type storage_t = unit;
type return_t = (list(operation) , storage_t);
```
(parameter_store: (my_type, my_store_type)) : (list(operation), my_store_type)
```
<!--END_DOCUSAURUS_CODE_TABS-->
This means that every smart contract needs at least one entrypoint function, here's an example:
Each main function receives two arguments:
- `parameter` - this is the parameter received in the invocation operation
- `storage` - this is the current (real) on-chain storage value
Storage can only be modified by running the smart contract entrypoint, which is responsible for returning a pair holding a list of operations, and a new storage.
Here is an example of a smart contract main function:
> 💡 The contract below literally does *nothing*
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=a
type parameter is unit;
type store is unit;
function main(const parameter: parameter; const store: store): (list(operation) * store) is
block { skip } with ((nil : list(operation)), store)
function main(const parameter: parameter_t; const store: storage_t): return_t is
((nil : list(operation)), store)
```
<!--CameLIGO-->
```cameligo group=a
type parameter = unit
type store = unit
let main (parameter, store: parameter * store) : operation list * store =
let main (parameter, store: parameter_t * storage_t) : return_t =
(([]: operation list), store)
```
<!--ReasonLIGO-->
```reasonligo group=a
type parameter = unit;
type store = unit;
let main = ((parameter, store): (parameter, store)) : (list(operation), store) => {
let main = ((parameter, store): (parameter_t, storage_t)) : return_t => {
(([]: list(operation)), store);
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
Each entrypoint function receives two arguments:
- `parameter` - this is the parameter received in the invocation operation
- `storage` - this is the current (real) on-chain storage value
A contract entrypoints are the constructors of the parameter type (variant) and you must use pattern matching (`case`, `match`, `switch`) on the parameter in order to associate each entrypoint to its corresponding handler.
Storage can only be modified by running the smart contract entrypoint, which is responsible for returning a list of operations, and a new storage at the end of it's execution.
To access the 'entrypoints' of a contract, we define a main function whose parameter is a variant type with constructors for each entrypoint. This allows us to satisfy the requirement that LIGO contracts always begin execution from the same function. The main function simply takes this variant, pattern matches it to determine which entrypoint to dispatch the call to, then returns the result of executing that entrypoint with the projected arguments.
> The LIGO variant's are compiled to a Michelson annotated tree of union type.
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=recordentry
type parameter_t is
| Entrypoint_a of int
| Entrypoint_b of string
type storage_t is unit
type return_t is (list(operation) * storage_t)
function handle_a (const p : int; const store : storage_t) : return_t is
((nil : list(operation)), store)
function handle_b (const p : string; const store : storage_t) : return_t is
((nil : list(operation)), store)
function main(const parameter: parameter_t; const store: storage_t): return_t is
case parameter of
| Entrypoint_a (p) -> handle_a(p,store)
| Entrypoint_b (p) -> handle_b(p,store)
end
```
<!--CameLIGO-->
```cameligo group=recordentry
type parameter_t =
| Entrypoint_a of int
| Entrypoint_b of string
type storage_t = unit
type return_t = (operation list * storage_t)
let handle_a (parameter, store: int * storage_t) : return_t =
(([]: operation list), store)
let handle_b (parameter, store: string * storage_t) : return_t =
(([]: operation list), store)
let main (parameter, store: parameter_t * storage_t) : return_t =
match parameter with
| Entrypoint_a p -> handle_a (p,store)
| Entrypoint_b p -> handle_b (p,store)
```
<!--ReasonLIGO-->
```reasonligo group=recordentry
type parameter_t =
| Entrypoint_a(int)
| Entrypoint_b(string);
type storage_t = unit;
type return_t = (list(operation) , storage_t);
let handle_a = ((parameter, store): (int, storage_t)) : return_t => {
(([]: list(operation)), store); };
let handle_b = ((parameter, store): (string, storage_t)) : return_t => {
(([]: list(operation)), store); };
let main = ((parameter, store): (parameter_t, storage_t)) : return_t => {
switch (parameter) {
| Entrypoint_a(p) => handle_a((p,store))
| Entrypoint_b(p) => handle_b((p,store))
}
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Built-in contract variables

View File

@ -15,10 +15,10 @@ Each `block` needs to include at least one `instruction`, or a *placeholder* ins
```pascaligo skip
// shorthand syntax
block { skip }
block { a := a + 1 }
// verbose syntax
begin
skip
a := a + 1
end
```
@ -29,10 +29,11 @@ end
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
Functions in PascaLIGO are defined using the `function` keyword followed by their `name`, `parameters` and `return` type definitions.
Here's how you define a basic function that accepts two `ints` and returns a single `int`:
Functions in PascaLIGO are defined using the `function` keyword
followed by their `name`, `parameters` and `return` type definitions.
Here's how you define a basic function that accepts two `int`s and
returns a single `int`:
```pascaligo group=a
function add(const a: int; const b: int): int is
@ -48,8 +49,10 @@ The function body consists of two parts:
#### Blockless functions
Functions that can contain all of their logic into a single instruction/expression, can be defined without the surrounding `block`.
Instead, you can inline the necessary logic directly, like this:
Functions that can contain all of their logic into a single
instruction/expression, can be defined without the surrounding
`block`. Instead, you can inline the necessary logic directly, like
this:
```pascaligo group=b
function add(const a: int; const b: int): int is a + b
@ -57,72 +60,78 @@ function add(const a: int; const b: int): int is a + b
<!--CameLIGO-->
Functions in CameLIGO are defined using the `let` keyword, like value bindings.
The difference is that after the value name a list of function parameters is provided,
along with a return type.
Functions in CameLIGO are defined using the `let` keyword, like value
bindings. The difference is that after the value name a list of
function parameters is provided, along with a return type.
CameLIGO is a little different from other syntaxes when it comes to function
parameters. In OCaml, functions can only take one parameter. To get functions
with multiple arguments like we're used to in traditional programming languages,
a technique called [currying](https://en.wikipedia.org/wiki/Currying) is used.
Currying essentially translates a function with multiple arguments into a series
of single argument functions, each returning a new function accepting the next
argument until every parameter is filled. This is useful because it means that
CameLIGO can support [partial application](https://en.wikipedia.org/wiki/Partial_application).
CameLIGO is a little different from other syntaxes when it comes to
function parameters. In OCaml, functions can only take one
parameter. To get functions with multiple arguments like we are used
to in traditional programming languages, a technique called
[currying](https://en.wikipedia.org/wiki/Currying) is used. Currying
essentially translates a function with multiple arguments into a
series of single argument functions, each returning a new function
accepting the next argument until every parameter is filled. This is
useful because it means that CameLIGO can support
[partial application](https://en.wikipedia.org/wiki/Partial_application).
Currying is however *not* the preferred way to pass function arguments in CameLIGO.
While this approach is faithful to the original OCaml, it's costlier in Michelson
than naive function execution accepting multiple arguments. Instead for most
functions with more than one parameter we should place the arguments in a
[tuple](language-basics/sets-lists-tuples.md) and pass the tuple in as a single
parameter.
Currying is however *not* the preferred way to pass function arguments
in CameLIGO. While this approach is faithful to the original OCaml,
it's costlier in Michelson than naive function execution accepting
multiple arguments. Instead for most functions with more than one
parameter we should place the arguments in a
[tuple](language-basics/sets-lists-tuples.md) and pass the tuple in as
a single parameter.
Here's how you define a basic function that accepts two `ints` and returns an `int` as well:
Here is how you define a basic function that accepts two `ints` and
returns an `int` as well:
```cameligo group=b
let add (a,b: int * int) : int = a + b
let add_curry (a: int) (b: int) : int = a + b
```
The function body is a series of expressions, which are evaluated to give the return
value.
The function body is a series of expressions, which are evaluated to
give the return value.
<!--ReasonLIGO-->
Functions in ReasonLIGO are defined using the `let` keyword, like value bindings.
The difference is that after the value name a list of function parameters is provided,
along with a return type.
Functions in ReasonLIGO are defined using the `let` keyword, like
value bindings. The difference is that after the value name a list of
function parameters is provided, along with a return type.
Here's how you define a basic function that accepts two `ints` and returns an `int` as well:
Here is how you define a basic function that accepts two `int`s and
returns an `int` as well:
```reasonligo group=b
let add = ((a,b): (int, int)) : int => a + b;
```
The function body is a series of expressions, which are evaluated to give the return
value.
The function body is a series of expressions, which are evaluated to
give the return value.
<!--END_DOCUSAURUS_CODE_TABS-->
## Anonymous functions
Functions without a name, also known as anonymous functions are useful in cases when you want to pass the function as an argument or assign it to a key in a record/map.
Functions without a name, also known as anonymous functions are useful
in cases when you want to pass the function as an argument or assign
it to a key in a record or a map.
Here's how to define an anonymous function assigned to a variable `increment`, with it's appropriate function type signature.
Here's how to define an anonymous function assigned to a variable
`increment`, with it is appropriate function type signature.
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=c
const increment : (int -> int) = (function (const i : int) : int is i + 1);
const increment : int -> int = function (const i : int) : int is i + 1;
// a = 2
const a: int = increment (1);
```
<!--CameLIGO-->
```cameligo group=c
let increment : (int -> int) = fun (i: int) -> i + 1
let increment : int -> int = fun (i: int) -> i + 1
```
<!--ReasonLIGO-->

View File

@ -3,19 +3,22 @@ id: maps-records
title: Maps, Records
---
So far we've seen pretty basic data types. LIGO also offers more complex built-in constructs, such as Maps and Records.
So far we have seen pretty basic data types. LIGO also offers more
complex built-in constructs, such as maps and records.
## Maps
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.
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.
Here's how a custom map type is defined:
Here is how a custom map type is defined:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
type move is (int * int);
type moveset is map(address, move);
type move is int * int
type moveset is map(address, move)
```
<!--CameLIGO-->
@ -32,7 +35,7 @@ type moveset = map(address, move);
<!--END_DOCUSAURUS_CODE_TABS-->
And here's how a map value is populated:
And here is how a map value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
@ -77,7 +80,10 @@ let moves : moveset =
### Accessing map values by key
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'll get will be wrapped as an optional; in our case `option(move)`. Here's an example:
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-->
@ -175,7 +181,7 @@ otherwise.
function iter_op (const m : moveset) : unit is
block {
function aggregate (const i : address ; const j : move) : unit is block
{ if (j.1 > 1) then skip else failwith("fail") } with unit ;
{ if j.1 > 1 then skip else failwith("fail") } with unit
} with map_iter(aggregate, m);
```
@ -202,7 +208,7 @@ let iter_op = (m: moveset): unit => {
```pascaligo
function map_op (const m : moveset) : moveset is
block {
function increment (const i : address ; const j : move) : move is block { skip } with (j.0, j.1 + 1) ;
function increment (const i : address ; const j : move) : move is (j.0, j.1 + 1);
} with map_map (increment, m);
```
@ -222,29 +228,30 @@ let map_op = (m: moveset): moveset => {
```
<!--END_DOCUSAURUS_CODE_TABS-->
`fold` is an aggregation function that return the combination of a maps contents.
`fold` is an aggregation function that return the combination of a
maps contents.
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.
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 {
function aggregate (const j : int ; const cur : (address * (int * int))) : int is j + cur.1.1 ;
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 =
let aggregate = fun (i,j: int * (address * (int * int))) -> i + j.1.1 in
Map.fold aggregate m 5
let aggregate = fun (i,j: int * (address * (int * int))) -> i + j.1.1
in Map.fold aggregate m 5
```
<!--ReasonLIGO-->
@ -268,13 +275,13 @@ 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.
Here's how we define a big map:
Here is how we define a big map:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
type move is (int * int);
type moveset is big_map(address, move);
type move is (int * int)
type moveset is big_map (address, move)
```
<!--CameLIGO-->
@ -291,13 +298,14 @@ type moveset = big_map(address, move);
<!--END_DOCUSAURUS_CODE_TABS-->
And here's how a map value is populated:
And here is how a map value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const moves: moveset = big_map
const moves: moveset =
big_map
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address) -> (1,2);
("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) -> (0,3);
end
@ -309,15 +317,16 @@ end
<!--CameLIGO-->
```cameligo
let moves: moveset = Big_map.literal
[ (("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address), (1, 2)) ;
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.
>
> `("<string value>": address)` means that we type-cast a string into an address.
> `("<string value>": address)` means that we cast a string into an address.
<!--ReasonLIGO-->
@ -330,24 +339,29 @@ let moves: moveset =
```
> Big_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.
> `("<string value>": address)` means that we cast a string into an address.
<!--END_DOCUSAURUS_CODE_TABS-->
### Accessing map values by key
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'll get will be wrapped as an optional; in our case `option(move)`. Here's an example:
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)];
const my_balance : option(move) =
moves [("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address)]
```
<!--CameLIGO-->
```cameligo
let my_balance : move option = Big_map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
let my_balance : move option =
Big_map.find_opt ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
<!--ReasonLIGO-->
@ -360,24 +374,28 @@ let my_balance : option(move) =
#### Obtaining a map value forcefully
Accessing a value in a map yields an option, however you can also get the value directly:
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);
const my_balance : move =
get_force (("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address), moves);
```
<!--CameLIGO-->
```cameligo
let my_balance : move = Big_map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
let my_balance : move =
Big_map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address) moves
```
<!--ReasonLIGO-->
```reasonligo
let my_balance : move = Big_map.find("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
let my_balance : move =
Big_map.find ("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": address, moves);
```
<!--END_DOCUSAURUS_CODE_TABS-->
@ -388,7 +406,8 @@ let my_balance : move = Big_map.find("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN": add
<!--Pascaligo-->
The values of a PascaLIGO big map can be updated using the ordinary assignment syntax:
The values of a PascaLIGO big map can be updated using the ordinary
assignment syntax:
```pascaligo
@ -400,7 +419,8 @@ function set_ (var m : moveset) : moveset is
<!--Cameligo-->
We can update a big map in CameLIGO using the `Big_map.update` built-in:
We can update a big map in CameLIGO using the `Big_map.update`
built-in:
```cameligo
@ -410,7 +430,8 @@ let updated_map : moveset =
<!--Reasonligo-->
We can update a big map in ReasonLIGO using the `Big_map.update` built-in:
We can update a big map in ReasonLIGO using the `Big_map.update`
built-in:
```reasonligo
let updated_map : moveset =
@ -421,17 +442,20 @@ let updated_map: moveset =
## Records
Records are a construct introduced in LIGO, and are not natively available in Michelson. The LIGO compiler translates records into Michelson `Pairs`.
Records are a construct introduced in LIGO, and are not natively
available in Michelson. The LIGO compiler translates records into
Michelson `Pairs`.
Here's how a custom record type is defined:
Here is how a custom record type is defined:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
type user is record
type user is
record
id : nat;
is_admin : bool;
name: string;
name : string
end
```
@ -440,7 +464,7 @@ end
type user = {
id : nat;
is_admin : bool;
name: string;
name : string
}
```
@ -455,15 +479,16 @@ type user = {
<!--END_DOCUSAURUS_CODE_TABS-->
And here's how a record value is populated:
And here is how a record value is populated:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo
const user: user = record
const user : user =
record
id = 1n;
is_admin = True;
name = "Alice";
name = "Alice"
end
```
@ -472,7 +497,7 @@ end
let user : user = {
id = 1n;
is_admin = true;
name = "Alice";
name = "Alice"
}
```
@ -486,10 +511,10 @@ let user: user = {
```
<!--END_DOCUSAURUS_CODE_TABS-->
### Accessing record keys by name
If we want to obtain a value from a record for a given key, we can do the following:
If we want to obtain a value from a record for a given key, we can do
the following:
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
@ -506,5 +531,4 @@ let is_admin : bool = user.is_admin
```reasonligo
let is_admin: bool = user.is_admin;
```
<!--END_DOCUSAURUS_CODE_TABS-->

View File

@ -3,13 +3,15 @@ id: sets-lists-tuples
title: Sets, Lists, Tuples
---
Apart from complex data types such as `maps` and `records`, ligo also exposes `sets`, `lists` and `tuples`.
Apart from complex data types such as `maps` and `records`, ligo also
exposes `sets`, `lists` and `tuples`.
> ⚠️ Make sure to pick the appropriate data type for your use case; it carries not only semantic but also gas related costs.
## Sets
Sets are similar to lists. The main difference is that elements of a `set` must be *unique*.
Sets are similar to lists. The main difference is that elements of a
`set` must be *unique*.
### Defining a set
@ -17,11 +19,7 @@ Sets are similar to lists. The main difference is that elements of a `set` must
<!--Pascaligo-->
```pascaligo group=a
type int_set is set (int);
const my_set: int_set = set
1;
2;
3;
end
const my_set : int_set = set 1; 2; 3 end
```
<!--CameLIGO-->
@ -45,8 +43,8 @@ let my_set: int_set =
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=a
const my_set: int_set = set end;
const my_set_2: int_set = set_empty;
const my_set: int_set = set end
const my_set_2: int_set = set_empty
```
<!--CameLIGO-->
```cameligo group=a
@ -63,7 +61,7 @@ let my_set: int_set = (Set.empty: set(int));
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=a
const contains_three: bool = my_set contains 3;
const contains_three : bool = my_set contains 3
// or alternatively
const contains_three_fn: bool = set_mem (3, my_set);
```
@ -84,7 +82,7 @@ let contains_three: bool = Set.mem(3, my_set);
<!--DOCUSAURUS_CODE_TABS-->
<!--Pascaligo-->
```pascaligo group=a
const set_size: nat = size(my_set);
const set_size: nat = size (my_set)
```
<!--CameLIGO-->