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 pairs.

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 ors 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 ors 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 TransferParamss:

-- 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.