Values, functions and types
This page covers the building blocks of Elci: basic values and their types, operators, functions and their signatures, lambdas, partial application and generic functions. Every example is a complete module that you can run.
Set up a playground
Section titled “Set up a playground”A function project runs one value with elci eval, which makes it the quickest way to try things out:
elci init playground --purpose functioncd playgroundelci eval .42cpu 16100, mem 200The project’s elci.toml ends with an [entrypoint] table, which points elci eval at the value main in
module Main:
[entrypoint]module = "Main"function = "main"purpose = "function"and src/Main.elm is:
module Main exposing (main)
main : Intmain = 42To try an example from these pages, replace src/Main.elm with it and run elci eval . again. Keep the
module Main exposing (main) line and a main value, because that’s what elci.toml names.
elci eval compiles main to a Plutus script, runs it on Julc’s virtual machine and prints:
- the value, written in Elci syntax;
- the execution budget the script used, in CPU and memory units. The ledger charges fees for these units;
- a
trace:line for eachDebug.tracemessage, if there are any.
Add --builtins to also list every Plutus builtin function that the script called.
Basic types
Section titled “Basic types”| Type | Examples | Notes |
|---|---|---|
Int | 42, -7, 1000000 | Whole numbers of any size. There is no Float. |
Bool | True, False | |
String | "GOLD", "line\n" | Text. Join with ++, compare with == or <. The String module has String.fromInt and more. |
Bytes | Bytes.fromString "GOLD" | Raw byte strings, from import Bytes |
() | () | Unit: a type with exactly one value, used for “nothing to say” |
Strings support the escapes \n, \r, \t, \" and \\. There is no Char type.
Values and functions are both defined with =, each with a type signature above it. A function lists its
parameters before the =:
module Main exposing (main)
lovelacePerAda : IntlovelacePerAda = 1000000
toLovelace : Int -> InttoLovelace ada = ada * lovelacePerAda
main : Intmain = toLovelace 25 + 500000elci eval . --builtins25500000cpu 480261, mem 1904builtin: MultiplyInteger(25, 1000000) → 25000000builtin: AddInteger(25000000, 500000) → 25500000The builtin lines show the two operations the script performed. A lovelace is the smallest unit of ada,
and on-chain amounts are always whole lovelace, which is one reason Elci doesn’t need Float.
Numbers
Section titled “Numbers”Int has arbitrary precision: +, - and * are exact and never overflow.
module Main exposing (main)
main : Intmain = 1000000000 * 1000000000 * 10000000001000000000000000000000000000cpu 16100, mem 200There are three ways to divide:
| Operation | Result |
|---|---|
a // b | Integer division, truncating toward zero: 7 // 2 is 3 and -7 // 2 is -3 |
modBy d n | Remainder with the sign of the divisor d: modBy 3 -7 is 2 |
remainderBy d n | Remainder with the sign of the dividend n: remainderBy 3 -7 is -1 |
module Main exposing (main)
main : ( Int, Int, Int )main = ( -7 // 2, modBy 3 -7, remainderBy 3 -7 )( -3, 2, -1 )cpu 1377500, mem 4659^ raises an integer to a power, and the prelude, which every module imports, provides the other everyday
functions: negate, abs, min, max and clamp.
module Main exposing (main)
main : List Intmain = [ 2 ^ 10, negate 5, abs -4, min 3 7, max 3 7, clamp 0 100 250 ][1024, -5, 4, 3, 7, 100]cpu 9197162, mem 37659clamp low high x keeps x between the bounds. Core modules lists the
whole prelude.
Dividing by zero (with //, modBy or remainderBy) doesn’t return a value: it fails the script, and a
failing validator rejects the transaction. / and fractional literals such as 1.5 are rejected by the
compiler:
main : Intmain = 7 / 2.../src/Main.elm:6:7: ELCI2001: Float division is unsupported; use // for integer quotient
6| 7 / 2 ^To work with fractions, scale your integers. For example, store a percentage in basis points (1% is 100).
Booleans and comparisons
Section titled “Booleans and comparisons”| Operator | Meaning |
|---|---|
==, /= | Equal, not equal. Works on numbers, strings, bytes, lists, tuples, records, custom types and dictionaries, but not on functions or Maps |
<, <=, >, >= | Ordering, for comparable types: numbers, strings, bytes, and lists and tuples of them |
&&, || | And, or. The right side is evaluated only when needed |
not | Negation: not True is False |
module Main exposing (main)
isValidFee : Int -> BoolisValidFee fee = fee >= 155381 && fee <= 2000000
main : Boolmain = isValidFee 200000 && not (isValidFee 5) && "ada" < "lovelace"Truecpu 1051432, mem 5116Strings order by their UTF-8 bytes, so "Zebra" < "apple": capital letters come first. compare a b
returns LT, EQ or GT. Booleans, records and custom types have no order, and True < False is a
compile error.
Comparisons can’t be chained. Write low < x && x < high instead of low < x < high:
main : Boolmain = 1 < 2 < 3.../src/Main.elm:6:11: ELCI1002: Comparison operators cannot be chained; use && or explicit parentheses
6| 1 < 2 < 3 ^Functions and signatures
Section titled “Functions and signatures”A call lists its arguments after the function name, separated by spaces: toLovelace 25,
isValidFee 200000. Parentheses group expressions, not arguments: not (isValidFee 5).
Every top-level declaration needs a type signature. In a signature, -> separates the parameter types
from the result type. Int -> Int -> Int takes two integers and returns one. Leaving a signature out is an
error:
double x = x * 2.../src/Main.elm:4:1: ELCI2004: Top-level declarations require a type signature
4| double x = ^^^^^^Inside a function, types are inferred, so local values need no annotations (you may still add them). When types don’t fit together, the compiler shows you where and suggests a fix:
main : Intmain = 1 + "one".../src/Main.elm:6:7: ELCI2002: The operator + works on Int values, not String; use ++ to append
6| 1 + "one" ^The first line gives the file, line and column, and a code you can look up in the
diagnostics reference. Below it are the line itself, with a caret under the
problem, and sometimes a Hint: line with the likely fix.
Lambdas, currying and partial application
Section titled “Lambdas, currying and partial application”\x -> expression is an anonymous function, or lambda. \n total -> n + total takes two arguments.
Every function takes its arguments one at a time. add : Int -> Int -> Int is a function that takes an
Int and returns a function Int -> Int, so you can supply only the first argument:
module Main exposing (main)
add : Int -> Int -> Intadd x y = x + y
addFive : Int -> IntaddFive = add 5
main : Intmain = List.foldl add 0 (List.map addFive [ 1, 2, 3 ])21cpu 6795728, mem 31808addFive is add with its first argument already supplied. List.map addFive adds 5 to each element
(giving [ 6, 7, 8 ]), and List.foldl add 0 sums them.
Operators are functions too. Written in parentheses, (+) is the function \a b -> a + b, so
List.foldl (+) 0 also sums a list, and (*) 2 doubles a number.
Elci is strict: a partial application evaluates the arguments you give it straight away. add (10 // 0)
fails at that point, not later when the second argument arrives.
Generic functions
Section titled “Generic functions”A lowercase name in a signature, such as a, is a type variable: the function works for any type in that
position.
module Main exposing (main)
twice : (a -> a) -> a -> atwice transform value = transform (transform value)
main : Intmain = if twice (\word -> word ++ "!") "go" == "go!!" then twice (\n -> n * 10) 3
else 0300cpu 1189600, mem 6013twice is used with strings and with integers. The compiler specializes a generic function for each
concrete type it’s used with.
A signature can’t promise more than the code delivers. This one claims to work for any a, but x + 0 only
works for Int:
identity : a -> aidentity x = x + 0.../src/Main.elm:4:1: ELCI2002: The annotation of `identity` says `a` can be any type, but its definition needs `Int`
4| identity : a -> a ^^^^^^^^Hint: write `Int` in the annotation, or make the definition work for any typeTo compare values of a type variable with <, or to join them with ++, name the variable
comparable or appendable, as in Elm: largest : comparable -> comparable -> comparable works for
numbers, strings and bytes. The language reference
covers these names.
Bytes and unit
Section titled “Bytes and unit”Byte strings are everywhere on Cardano: key hashes, token names and script hashes. The Bytes module wraps
the Plutus byte-string builtins:
module Main exposing (main)
import Bytes
tokenName : BytestokenName = Bytes.fromString "GOLD"
main : Intmain = Bytes.length tokenName + Bytes.length (Bytes.sha256 tokenName)elci eval . --builtins36cpu 786669, mem 2032builtin: EncodeUtf8("GOLD") → #474f4c44builtin: LengthOfByteString(#474f4c44) → 4builtin: Sha2_256(#474f4c44) → #fb5b8ae0fd37258b...builtin: LengthOfByteString(#fb5b8ae0fd37258b...) → 32builtin: AddInteger(4, 32) → 36“GOLD” is the 4 bytes #474f4c44, and its SHA-256 hash is 32 bytes. Byte strings are written in hex after
a #. Bytes, hashes and bits covers the whole module.
The unit value () carries no information. You met it in Your first validator
as a datum and redeemer that the validator ignores.
Comments
Section titled “Comments”module Main exposing (main)
answer : Intanswer = -- A line comment. 42
{- A block comment, which can span lines.-}
{-| A documentation comment, directly above a declaration.-}main : Intmain = answerConditionals, let and pipelines shows how to make decisions, name intermediate values and chain function calls.