A Taste of Agda
The objective of this section is to provide a first glimpse of Agda with some small examples. The first one is a demonstration of dependently typed programming, and the second shows how to use Agda as a proof assistant. Finally, we build a complete program and compile it to an executable program with the GHC and Javascript backends.
Preliminaries
Before proceeding, make sure that you installed Agda and a compatible version of the standard library.
Agda programs are typically developed interactively, which means that one can type check code which is not yet complete but contain “holes” which can be filled in later. Editors with support for interactive development of Agda programs include Emacs via the Emacs mode, Atom via the agda mode for Atom, Visual Studio Code via the agda mode for VSCode, and Vim via agda-vim.
Hint
If you want a sneak peek of Agda without installing it, try the Agda Pad
Note
In this introduction we use several of Agda’s interactive commands to get information from the typechecker and manipulate code with holes. Here is a list of the commands that will be used in this tutorial:
C-c C-l
: Load the file and type-check it.C-c C-d
: Deduce the type of a given expression.C-c C-n
: Normalise a given expression.C-c C-,
: Shows the type expected in the current hole, along with the types of any local variables.C-c C-c
: Case split on a given variable.C-c C-SPC
: Replace the hole with a given expression, if it has the correct type.C-c C-r
: Refine the hole by replacing it with a given expression applied to an appropriate number of new holes.C-c C-x C-c
(C-x C-c
in VS Code): Compile an Agda program.
See Notation for key combinations for a full list of interactive commands (keybindings).
Programming With Dependent Types: Vectors
In the code below, we model the notion of vectors (in the sense of computer science, not in the mathematical sense) in Agda. Roughly speaking, a vector is a list of objects with a determined length.
module hello-world-dep where
open import Data.Nat using (ℕ; zero; suc)
data Vec (A : Set) : ℕ → Set where
[] : Vec A zero
_∷_ : ∀ {n} (x : A) (xs : Vec A n) → Vec A (suc n)
infixr 5 _∷_
Paste or type the code above in a new file with name hello-world-dep.agda
. Load the file (in Emacs C-c C-l
). This also saves the file. If the agda source code was loaded correctly, you should see that the code is highlighted and see a message *All done* .
Note
If a file does not type check Agda will complain. Often the cursor will jump to the position of the error, and the error will (by default) be underlined. Some errors are treated a bit differently, though. If Agda cannot see that a definition is terminating/productive it will highlight it in light salmon, and if some meta-variable other than the goals cannot be solved the code will be highlighted in yellow (the highlighting may not appear until after you have reloaded the file). In case of the latter kinds of errors you can still work with the file, but Agda will (by default) refuse to import it into another module, and if your functions are not terminating Agda may hang. See Background highlighting for a full list of the different background colors used by Agda.
Tip
If you do not like the way Agda syntax or errors are highlighted (if you are colour-blind, for instance), then you can tweak the settings by typing M-x customize-group RET agda2-highlight RET
in Emacs (after loading an Agda file) and following the instructions.
Agda programs are structured into modules. Each Agda file has one top-level module whose name must match the name of the file, and zero or more nested modules. Each module contains a list of declarations. This example has a single top-level module called hello-world-dep
, which has three declarations:
An
open import
statement that imports the datatypeℕ
and its constructorszero
andsuc
from the moduleData.Nat
of the standard library and brings them into scope,A
data
declaration defining the datatypeVec
with two constructors: the empty vector constructor[]
and the cons constructor_∷_
,And finally an
infixr
declaration specifying the precedence for the cons operation.
Tip
Agda uses Unicode characters in source files (more specifically: the UTF-8 character encoding), such as ℕ
, →
, and ∷
in this example. Many mathematical symbols can be typed using the corresponding LaTeX command names. To learn how to enter a unicode character, move the cursor over it and enter M-x describe-char
or C-u C-x =
. This displays all information on the character, including how to input it with the Agda input method. For example, to input ℕ
you can type either \Bbb{N}
or \bN
. See Unicode input for more details on entering unicode characters.
The datatype Vec
Let us start by looking at the first line of the definition of Vec
:
data Vec (A : Set) : ℕ → Set where
This line declares a new datatype and names it Vec
. The words data
and where
are keywords, while the part Vec (A : Set) : ℕ → Set
determines the type of Vec
.
Vec
is not a single type but rather a family of types. This family of types has one parameter A
of type Set
(which is the sort of small types, such as ℕ
, Bool
, …) and one index of type ℕ
(the type of natural numbers). The parameter A
represents the type of the objects of the vector. Meanwhile, the index represents the length of the vector, i.e. the number of objects it contains.
Together, this line tells us that, for any concrete type B : Set
and any natural number m : ℕ
, we are declaring a new type Vec B m
, which also belongs to Set
.
The constructors []
and _∷_
Each constructors of a datatype is declared on a separate line and indented with a strictly positive number of spaces (in this case two).
We chose the name []
for the first constructor. It represents the empty vector, and its type is Vec A 0
, i.e. it is a vector of length 0
.
The second constructor is a mixfix operator named _∷_
(pronounced cons). For any number n : ℕ
, it takes as input an object of A
and a vector of length n
. As output, it produces a vector with length suc n
, the successor of n
. The number n
itself is an implicit argument to the constructor _∷_
.
The final declaration with keyword ìnfixr
does not belong to the datatype declaration itself; therefore it is not indented. It establishes the precedence of the operator _∷_
.
Tip
You can let Agda infer the type of an expression using the ‘Deduce type’ command (C-c C-d
). First press C-c C-d
to open a prompt, enter a term, for instance 3 ∷ 2 ∷ 1 ∷ []
, and press return. Agda infers its type and return the type Vec ℕ 3
, meaning that the given term is a vector with 3 objects of type ℕ
.
Note
Almost any character can be used in an identifier (like α
, ∧
, or ♠
, for example). It is therefore necessary to have spaces between most lexical units. For example 3∷2∷1∷[]
is a valid identifier, so we need to write 3 ∷ 2 ∷ 1 ∷ []
instead to make Agda parse it successfully.
The total function lookup
Now that Vec
is defined, we continue by defining the lookup
function that given a vector and a position, returns the object of the vector at the given position. In contrast to the lookup
function we could define in most (non-dependently typed) programming languages, this version of the function is total: all calls to it are guaranteed to return a value in finite time, with no possibility for errors.
To define this function, we use the Fin
datatype from the standard library. Fin n
is a type with n
objects: the numbers 0
to n-1
(in unary notation zero
, suc zero
, …), which we use to model the n
possible positions in a vector of length n
.
Now create a new file called hello-world-dep-lookup.agda
file and type or paste:
module hello-world-dep-lookup where
open import Data.Nat using (ℕ)
open import Data.Vec using (Vec; _∷_)
open import Data.Fin using (Fin; zero; suc)
variable
A : Set
n : ℕ
lookup : Vec A n → Fin n → A
lookup (a ∷ as) zero = a
lookup (a ∷ as) (suc i) = lookup as i
The Vec
type that we saw before is actually already in the module Data.Vec
of the standard library, so we import it instead of copying the previous definition.
We have declared A
and n
as generalizable variables to avoid the declaration of implicit arguments. This allows us to use A
and n
in the type of lookup
without binding the names explicitly. More explicitly, the full type of lookup
(which we can get by using C-c C-d
) is:
lookup : {A : Set} {n : ℕ} → Vec A n → Fin n → A
Warning
zero
and suc
are not the constructors of ℕ
that we saw before, but rather the constructors of Fin
. Agda allows overloading of constructor names, and disambiguates between them based on the expected type where they are used.
The definition of the lookup
function specifies two cases:
Either the vector is
a ∷ as
and the position iszero
, so we return the first objecta
of the vector.Or the vector is
a ∷ as
and the position issuc i
, so we recursively look up the object at positioni
in the tailas
of the vector.
There are no cases for the empty vector []
. This is no mistake: Agda can determine from the type of lookup
that it is impossible to look up an object in the empty vector, since there is no possible index of type Fin 0
. For more details, see the section on coverage checking.
Agda as a Proof Assistant: Proving Associativity of Addition
In this section we state and prove the associativity of addition on the natural numbers in Agda. In contrast to the previous section, we build the code line by line. To follow along with this example in Emacs, reload the file after adding each step by pressing C-c C-l
.
Statement of associativity
We start by creating a new file named hello-world-proof.agda
. Paste or type the following code:
module hello-world-proof where
Now we import the datatype ℕ
and the addition operation _+_
, both defined in the Agda Builtin library.
open import Data.Nat using (ℕ; _+_)
Next, we import the propositional equality type _≡_
from the module Relation.Binary.PropositionalEquality
.
open import Relation.Binary.PropositionalEquality using (_≡_)
Under the Curry-Howard correspondence, the type x ≡ y
corresponds to the proposition stating that x
and y
are equal objects. By writing a function that returns an object of type x ≡ y
, we are proving that the two terms are equal.
Now we can state associativity: given three (possibly different) natural numbers, adding the first to the addition of the second and the third computes to the same value as adding the addition of the first and the second to the third. We name this statement +-assoc
.
+-assoc : Set
+-assoc = ∀ (x y z : ℕ) → x + (y + z) ≡ (x + y) + z
This is not yet a proof, we have merely written down the statement (or enunciation) of associativity.
Proof of associativity
The statement +-assoc
is a member of Set
, i.e. it is a type. Now that we have stated the property in a way that Agda understands, our objective is to prove it. To do so, we have to construct a function of type +-assoc
.
First, we need to import the constructors zero
and suc
of the already imported datatype ℕ
and the constructor refl
(short for reflexivity) and function cong
(short for congruence) from the standard library.
open import Data.Nat using (zero; suc)
open import Relation.Binary.PropositionalEquality using (refl; cong)
To prove +-assoc
we need to find an object of that type. Here, we name this object +-assoc-proof
.
+-assoc-proof : ∀ (x y z : ℕ) → x + (y + z) ≡ (x + y) + z
If we load now the file, Agda gives an error: “The following names are declared but not accompanied by a definition: +-assoc-proof
”. Indeed, we have only declared the type of +-assoc-proof
but not yet given a definition. To build the definition, we need to know more about holes and case splitting.
Holes and case splitting
We can let Agda help us to write the proof by using its interactive mode. To start, we first write a simple clause so the file can be loaded even if we still do not know the proof. The clause consists of the name of the property, the input variables, the equals symbol =
and the question mark ?
.
+-assoc-proof x y z = ?
When we reload the file, Agda no longer throws an error, but instead shows the message *All Goals* with a list of goals. We have now entered the interactive proving mode. Agda turns our question mark into what is called a hole { }0
with a label 0
. Each hole stands as a placeholder for a part of the program that is still incomplete and can be refined or resolved interactively.
Note
You are not supposed to enter a hole such as { }0
manually, Agda takes care of the numbering when you load the file. To insert a hole, write either ?
or {! !}
and load the file to make Agda assign a unique number to it.
To get detailed information about a specific hole, put the cursor in it and press C-c C-,
. This displays the type of the hole, as well as the types of all the variables in scope. In this example we get the information that the goal type is x + (y + z) ≡ x + y + z
, and there are three variables x
, y
, and z
in scope, all of type ℕ
.
Note
You might wonder why Agda displays the term (x + y) + z
as x + y + z
(without parenthesis). This is done because of the infix statement infixl 6 _+_
that was declared in the imported Agda.Builtin.Nat
module. This declaration means that the _+_
operation is left-associative. More information about mixfix operator like the arithmetic operations. You can also check this associativity example.
To continue writing our proof, we now pick a variable and perform a case split on it. To do so, put the cursor inside the hole and press C-c C-c
. Agda asks for the name of the pattern variable to case on. Let’s write x
and press return. This replaces the previous clause with two new clauses, one where x
has been replaced by zero
and another where it has been replaced by suc x
:
+-assoc-proof zero y z = { }0
+-assoc-proof (suc x) y z = { }1
Important
The x
in the type signature of +-assoc-proof
is not the same as the x
pattern variable in the last clause where suc x
is written. The following would also work: +-assoc-proof (suc x₁) y z = { }1
. The scope of a variable declared in a signature is restricted to the signature itself.
Instead of one hole, we now have two. The first hole has type y + z ≡ y + z
, which is easy to resolve. To do so, put the cursor inside the first hole labeled 0
and press C-c C-r
. This replaces the hole by the term refl
, which stands for reflexivity and can be used any time we want to construct a term of type w ≡ w
for some term w
.
+-assoc-proof zero y z = refl
+-assoc-proof (suc x) y z = { }1
Now we have one hole left to resolve. By putting the cursor in it and pressing C-c C-,
again, we get the type of the hole: suc x + (y + z) ≡ suc x + y + z
. Agda has already applied the definition of _+_
to replace the left-hand side (suc x + y) + z
of the equation by suc (x + y + z)
, and similarly replaced the right-hand side suc x + (y + z)
by suc (x + (y + z))
.
Tip
You can use the go-to-definition
command by selecting the definition that you want to check eg. _+_
and pressing M-.
in Emacs or C-M-\
in Atom. This takes you to the definition of _+_
, which is originally defined in the builtin module Agda.Builtin.Nat
.
Tip
You can ask Agda to compute the normal form of a term. To do so, place the cursor in the remaining hole (which should not contain any text at this point) and press C-c C-n
. This prompts you for an expression to normalize. For example, if we enter (suc x + y) + z
we get back suc (x + y + z)
as a result.
Proof by induction
If we now look at the type of the remaining hole, we see that both the left-hand side and the right-hand side start with an application of the constructor suc
. In this kind of situation it suffices to prove that the two arguments to suc
are equal. This principle is called congruence of equality _≡_
, and it is expressed by the Agda function cong
.
To use cong
we need to apply it to a function or constructor, in this case suc
. If we ask Agda to infer the type of cong suc
by pressing C-c C-d
and entering the term, we get back the type {x y : ℕ} → x ≡ y → suc x ≡ suc y
. In other words, cong suc
takes as input a proof of an equality between x
and y
and produces a new proof of equality between suc x
and suc y
. We write cong suc
in the hole and again press C-c C-r
to refine the hole. This results in the new line
+-assoc-proof (suc x) y z = cong suc { }2
where the new hole with number 2 is of type x + (y + z) ≡ x + y + z
.
To finish the proof, we now make a recursive call +-assoc-proof x y z
. Note that this has type x + (y + z) ≡ (x + y) + z
, which is exactly what we need. To complete the proof, we type +-assoc-proof x y z
into the hole and solve it with C-c C-space
. This replaces the hole with the given term and completes the proof.
Note
When we define a recursive function like this, Agda performs termination checking on it. This is important to ensure the recursion is well-founded, and hence will not result in an invalid (circular) proof. In this case, the first argument x
is structurally smaller than the first argument suc x
on the left-hand side of the clause, hence Agda allows us to make the recursive call. Because termination is an undecidable property, Agda will not accept all terminating functions, but only the ones that are mechanically proved to terminate.
The final proof +-assoc-proof
is defined as follows:
+-assoc-proof zero y z = refl
+-assoc-proof (suc x) y z = cong suc (+-assoc-proof x y z)
When we reload the file, we see *All Done*. This means that +-assoc-proof
is indeed a proof of the statement +-assoc
.
Here is the final code of the ‘Hello world’ proof example, with all imports together at the top of the file:
module hello-world-proof where
open import Data.Nat using (ℕ; zero; suc; _+_)
open import Relation.Binary.PropositionalEquality using (_≡_; refl; cong)
+-assoc : Set
+-assoc = ∀ (x y z : ℕ) → x + (y + z) ≡ (x + y) + z
+-assoc-proof : ∀ (x y z : ℕ) → x + (y + z) ≡ (x + y) + z
+-assoc-proof zero y z = refl
+-assoc-proof (suc x) y z = cong suc (+-assoc-proof x y z)
Tip
You can learn more details about proving in the chapter Proof by Induction of the online book Programming Language Foundations in Agda.
Building an Executable Agda Program
Agda is a dependently typed functional programming language. This means that we can write programs in Agda that interact with the world. In this section, we write a small ‘Hello world’ program in Agda, compile it, and execute it. In contrast to the standalone example on the Hello World page, here we make use of the standard library to write a shorter version of the same program.
Agda Source Code
First, we create a new file named hello-world-prog.agda
with Emacs or Atom in a folder that we refer to as our top-level folder.
{-# OPTIONS --guardedness #-}
module hello-world-prog where
open import IO
main : Main
main = run (putStrLn "Hello, World!")
A quick line-by-line explanation:
The first line is a pragma (a special comment) that specifies some options at the top of the file.
The second line declares the top-level module, named
hello-world-prog
.The third line imports the
IO
module from the standard library and brings its contents into scope.A module exporting a function
main
of typeMain
(defined in theIO
module of the standard library) can be compiled to a standalone executable. For example:main = run (putStrLn "Hello, World!")
runs theIO
commandputStrLn "Hello, World!"
and then quits the program.
Compilation with GHC Backend
Once we have loaded the program in Emacs or Atom, we can compile it directly by pressing C-c C-x C-c
and entering GHC
. Alternatively, we can open a terminal session, navigate to the top-level folder and run:
agda --compile hello-world-prog.agda
The --compile
flag here creates via the GHC backend a binary file in the top-level folder that the computer can execute.
Finally, we can then run the executable (./hello-world-prog
on Unix systems, hello-world-prog.exe
on Windows) from the command line:
$ cd <your top-level folder>
$ ./hello-world-prog
Hello, World!
Compilation with JavaScript Backend
The JavaScript backend translates the Agda source code of the hello-world-prog.agda
file to JavaScript code.
From Emacs or Atom, press C-c C-x C-c
and enter JS
to compile the module to JavaScript. Alternatively, open a terminal session, navigate to the top-level folder and run:
agda --js hello-world-prog.agda
This creates several .js
files in the top-level folder. The file corresponding to our source code has the name jAgda.hello-world-prog.js
.
Hint
The additional --js-optimize
flag can be used to make the generated JavaScript code faster but less readable. Moreover, the --js-minify
flag makes the generated JavaScript code smaller and even less readable.
Where to go from here?
There are many books and tutorials on Agda. We recommend this list of tutorials.
Join the Agda Community!
Get in touch and join the Agda community, or join the conversation on the Agda Zulip.