Types¶
Indigo does not use Michelson types directly, it instead uses types from Haskell that have a correspondence to Michelson's.
Core and domain-specific types¶
For each core and domain-specific Michelson type (see documentation) there is one directly corresponding in Indigo.
The following table contains all of them:
Michelson Type | Indigo Type |
---|---|
string |
MText |
nat |
Natural |
int |
Integer |
bytes |
ByteString |
bool |
Bool |
unit |
() |
list (t) |
[t] |
pair (l) (r) |
(l, r) |
option (t) |
Maybe t |
or (l) (r) |
Either l r |
set (t) |
Set c |
map (k) (t) |
Map k t |
big_map (k) (t) |
BigMap k t |
timestamp |
Timestamp |
mutez |
Mutez |
address |
Address |
contract 'param |
ContractRef param |
operation |
Operation |
key |
PublicKey |
key_hash |
KeyHash |
signature |
Signature |
chain_id |
ChainId |
ticket d |
Ticket d |
view
and void
type synonyms¶
TZIP-4 define 2 useful type synonyms for entrypoints, these are their direct Indigo counterparts:
Michelson Type | Indigo Type |
---|---|
view a r |
View a r |
void a b |
Void_ a b |
Note
These follow the same de-sugaring, please refer to the tzip documentation for more information.
custom/complex types¶
TZIP-4,
extended by TZIP-6,
define syntax sugar for balanced trees of or
and pair
s.
Indigo allows you to build and use custom types that translate to Michelson as these balanced trees.
This is an example of such a data type:
data SimpleCustom
= FirstC Integer
| SecondC Natural Natural
| ThirdC MText
deriving stock (Generic)
deriving anyclass (IsoValue)
The new type we defined here is called SimpleCustom
, it has 3 constructors (
FirstC
, SecondC
, ThirdC
), each of which has one or more fields of various types
(Integer
, Natural
, MText
).
Constructors with multiple types get translated to Michelson as pair
trees,
whereas types with multiple constructors get translated as or
trees.
To define your own type you can use the one above as a template and remember that:
- the name of the type and its constructors need to start with a capital letter
- you can give any supported type to each constructor, including other custom ones
- you can have more (or less) than 3 constructors, but do not forget the 2
deriving
lines at the end.
Note
Types that have multiple constructors are required to have only a single
field per contructor in order to wrap
and unwrap
values to/from it.
This limitation can however be simply avoided by creating another type with a single constructor and multiple fields and use this as the only field for a constructor.
case statements and entrypoints¶
These 2 TZIPs also define entrypoints as trees of or
s and a case
macro to
use them.
In Indigo we can declare custom types and use them with the case_
statement and
as entrypoints as well.
To explain this let's take a look to the custom type defined in the statements chapter of the Tutorial:
data IncrementIf
= IsZero Integer
| HasDigitOne Natural
deriving stock (Generic, Show)
deriving anyclass (IsoValue)
Notice that to satisfy the case_
instruction we need (by definition of the
CASE
macro in TZIP) each of the constructors to have exactly one field each,
given that it has to translate to a tree of or
s in Michelson.
In the same file you can also see the following two lines:
instance ParameterHasEntrypoints IncrementIf where
type ParameterEntrypointsDerivation IncrementIf = EpdPlain
These declare our custom type to be a "plain" parameter with entrypoints.
What you need to pay attention to (especially when defining your
own) is the EpdPlain
.
This means that we have all entrypoints defined directly in this type, one for each of its constructors (pointing at their respective types).
Another way to define entrypoints is to use EpdRecursive
instead of EpdPlain
.
This instead means that the entrypoints are defined in the nested fields of our type.
For example, using the same definition above of IncrementIf
, but not its
ParameterHasEntrypoints
instance
, we could additionally define:
data ActionsParam
= ActionIncrement IncrementIf
deriving stock (Generic, Show)
deriving anyclass (IsoValue)
instance ParameterHasEntrypoints ActionsParam where
type ParameterEntrypointsDerivation ActionsParam = EpdRecursive
This is useful to define entrypoints in smaller pieces and then merge them in a single parent type.
Note that EpdRecursive
only defines entrypoints for the leafs of the tree, not
the internal nodes, so in this case it will only create the ones for IncrementIf
's
constructors, not for ActionsParam
's.
One last way to define entrypoint is to use EpdDelegate
instead.
This works just like EpdRecursive
with the difference that the parent type
constructors also are entrypoints (so for the node of the tree) and it requires
the inner types to define their own ParameterHasEntrypoints
instance
.
Named types and Labels¶
Indigo provides another facility to define and use types: named types.
These are defined with the use of the :!
constructor to assign a name to an
existing type, or as tuple to assign one to each of the tuple's types.
For example, these are all valid named types:
"receiver" :! Address
("source" :! Address, "receiver" :! Address)
("source" :! Address, "receiver" :! Address, "amount" :! Natural)
The main convenience of named types is in the simple expressions to manipulate them, see the reference material about Record operators.
You can also associate them with a type name, that can be used as any other type, by creating a type synonym, for example:
type TransferParams = ("source" :! Address, "receiver" :! Address, "amount" :! Natural)
so that you can refer to it as a custom type as well.
Note that while we define named types with names enclosed between "
to be able
to manipulate them we need to use a specific type, called Label
.
Thanks to OverloadedLabels
, values of this type are simply created by
prefixing the name with a #
.
For example, this is some Indigo code interacting with TransferParams
s:
-- just a variable of type 'Address'
a <- new$ sender
-- creation of a variable of type TransferParams
tp <- new$ construct (a !~ #source, a !~ #receiver, 10 !~ #amount)
-- extracting the "amount" from a TransferParams
amount <- new$ tp #! #amount
-- updating the "amount" in a TransferParams
tp =: tp !! (#amount, 20)
Named types are often useful in making clear what is the use of a given type, for instance, going back to the example above of the statements chapter:
data IncrementIf
= IsZero Integer
| HasDigitOne Natural
deriving stock (Generic, Show)
deriving anyclass (IsoValue)
from this IncrementIf
definition it is not very clear what the Natural
in its
HasDigitOne
constructor is used for, but with named types it could have been
defined this way instead:
type Digits = "digits" :! Natural
data IncrementIf
= IsZero Integer
| HasDigitOne Digits
deriving stock (Generic, Show)
deriving anyclass (IsoValue)
Internal types and additional features¶
Apart from the ones described above, Indigo uses other types internally and has additional facilities to manipulate them, but they should not concern most users.
Haskell developers that wish to know more may start from the bottom of the chain by looking at morley's README.