Plutus Examples

Here we’ll take a look at some of the common examples of programs to give you a better feel of how the Plutus language works. We’ll implement Peano numerals, cons lists, and binary trees, as well as some common functions relating them.

To start with, let’s define Peano numerals:

data Nat = { Zero | Suc Nat }

The naturals support a variety of functions, of course, such as addition, multiplication, factorial, and Fibonacci, which are typical examples of programs.

add : Nat -> Nat -> Nat {
  add Zero n = n ;
  add (Suc m) n = Suc (add m n)

mul : Nat -> Nat -> Nat {
  mul Zero _ = Zero ;
  mul (Suc m) n = add (mul m n) n

fac : Nat -> Nat {
  fac Zero = Suc Zero ;
  fac (Suc n) = mul (Suc n) (fac n)

fib : Nat -> Nat {
  fib Zero = Suc Zero ;
  fib (Suc Zero) = Suc Zero ;
  fib (Suc (Suc n)) = add (fib n) (fib (Suc n))

Cons lists are also a familiar type:

data List a = { Nil | Cons a (List a) }

This demonstrates the use of parametric types, where List a has a type parameter a for the type of elements. So, for example, List Nat is the type of lists of Peano numerals.

Lists support a variety of functions, such as length, append, and map:

length : forall a. List a -> Nat {
  length Nil = Zero ;
  length (Cons _ xs) = Suc (length xs)

append : forall a. List a -> List a -> List a {
  append Nil ys = ys ;
  append (Cons x xs) ys = Cons x (append xs ys)

map : forall a b. (a -> b) -> List a -> List b {
  map _ Nil = Nil ;
  map f (Cons x xs) = Cons (f x) (map f xs)

Here we can see the use of polymorphism principle in Plutus. These functions work for any list, regardless of the element type, so we can abstract over the element type by using forall. For instance, the type of length says that for any choice of a, we have a function of type List a -> Nat.

It’s important to note that in Plutus, this polymorphism exists only for the declaration of values. Any time you use a polymorphically declared value, the choice of the type variable must be fixed at the use site. You can’t treat these declarations as giving polymorphic values in general, as in System-F. Rather, a polymorphic type in a declaration is an abbreviation for an infinite family of identical definitions that differ only in the choice of that type variable. For example, we could define multiple length functions like so:

lengthNat : List Nat -> Nat {
  lengthNat Nil = Zero ;
  lengthNat (Cons _ xs) = Suc (lengthNat xs)

lengthBool : List Bool -> Nat {
  lengtBool Nil = Zero ;
  lengthBool (Cons _ xs) = Suc (lengthBool xs)

lengthListNat : List (List Nat) -> Nat {
  lengthListNat Nil = Zero ;
  lengthListNat (Cons _ xs) = Suc (lengthListNat xs)

And they’re all identical except the name and the choice for a. This is of course redundant, so we can use the polymorphic declaration given above. But this declaration does not give us a value length with the type forall a. List a -> Nat. Instead, it gives us that entire infinite family of declarations, but with a convenient abbreviation syntax. This is why the use of such polymorphic declarations requires the choice of the type variables to be fixed at the use site.

Another common type is the type of binary trees with data in the branches:

data Tree a = { Leaf | Branch a (Tree a) (Tree a) }

Such trees support functions such as count, traversal, and reverse:

count : forall a. Tree a -> Nat {
  count Leaf = Zero ;
  count (Branch _ l r) = Suc (add (count l) (count r))

traversal : forall a. Tree a -> List a {
  traversal Leaf = Nil ;
  traversal (Branch x l r) = Cons x (append (traversal l) (traversal r))

reverse : forall a. Tree a -> Tree a {
  reverse Leaf = Leaf ;
  reverse (Branch x l r) = Branch x (reverse r) (reverse l)