Functions and Procedures

To promote reusability, readability and separation of code, Indigo supports functions and procedures. This chapter shows the syntax and expands on their usage.

Granular control

This chapter's source file is Functions.hs, that contains functionsContract:

module Indigo.Tutorial.Functions.Functions
  ( functionsContract
  ) where

import Indigo

data IncrementIf
  = IsZero Integer
  | HasDigitOne Natural
  deriving stock (Generic, Show)
  deriving anyclass (IsoValue)

instance ParameterHasEntrypoints IncrementIf where
  type ParameterEntrypointsDerivation IncrementIf = EpdPlain

functionsContract :: IndigoContract IncrementIf Natural
functionsContract param = defContract do
  result <- case_ param $
    ( #cIsZero //-> checkZero
    , #cHasDigitOne //-> checkHasDigitOne
  updateStorage result

checkZero :: Var Integer -> IndigoFunction Bool
checkZero val = defFunction do
  return (val == 0 int)

checkHasDigitOne :: Var Natural -> IndigoFunction Bool
checkHasDigitOne val = defFunction do
  base <- new$ 10 nat
  checkRes <- new$ False
  while (val > base && checkRes == False) do
    val =: val / base
    remainder <- new$ val % base
    checkRes =: remainder == 1 nat
  return checkRes

updateStorage :: HasStorage Natural => Var Bool -> IndigoProcedure
updateStorage result = defFunction do
  if result then storage =: 0 nat else incrementStorage

incrementStorage :: HasStorage Natural => IndigoProcedure
incrementStorage = defFunction do
  storage += 1 nat

storage :: HasStorage Natural => Var Natural
storage = storageVar

When converted to Michelson this program is equivalent to the controlContract from the previous chapter.

The difference is fully visible, in that the contract itself is much easier to read because it has been broken down into simple functions.

Let's look at their definition, starting from the first one:

checkZero :: Var Integer -> IndigoFunction Bool
checkZero val = defFunction do
  return (val == 0 int)

This function takes the place of the code-block we used to execute in the first pattern-match in controlContract, that looked like this:

    ( #cIsZero //-> \val -> do
        return (val == 0 int)

As you can see this is almost identical to the checkZero function's code.

The main difference is that we put val before an = and used defFunction, this should be easy to figure out, because defFunction is to functions what defContract is to ... contracts, as you can see from the first lines here:

functionsContract param = defContract do

checkZero val = defFunction do

The other difference is that by using functions we need to explicitly write a signature, but this is not hard, we can see that the ones for checkZero is:

checkZero :: Var Integer -> IndigoFunction Bool

Just as we did in the Basics and Variables chapter for contracts, we can use a formula to make function signatures:

<function_name> :: [Var parameter ->] IndigoFunction <return_type>

Here's what you need to know:

  • there can be 0 parameters as well as more than one.
  • The return type can be any of the supported types.

In addition to this there are IndigoProcedures, that are just like IndigoFunctions, differing only by the absence of a return value (or in other terms, they return ()) and in fact begin with the same defFunction.

That's it, quite simple isn't it?

This file also contains examples of Indigo functions with no parameters (e.g. incrementStorage), multiple parameters (e.g. checkHasDigitOne) and that access the storage (e.g. incrementStorage, note that just like storage these need to specify the correct HasStorage constraint).

You can define such Indigo functions and call them in different places, also note that just like the other imperative constructs they have (automatically managed) scope.

Now that we are armed with functions as well we can proceed to the final chapter: Side Effects and Errors.

Technical details: defFunction and defContract

As for IndigoContracts, both IndigoProcedures and IndigoFunctions are just specialized versions of IndigoM.

These types are all connected and exist mostly for convenience. The same can be said for defContract which is just a specialized version of defFunction that carries on all the required constraints.

defFunction is what makes this possible, as it adds scope management to the given arbitrary IndigoM, using the same logic mentioned in the previous chapter for imperative statements.

You can find the definitions for all of these types and functions in the Indigo.Compilation and Indigo.Frontend.Language modules.