The information on this page is likely to be out of date. See the official user manual for information on the latest version of Agda.

Overview of the language

What follows is a very brief introduction to the main features of the Agda language. Do not expect to understand everything if you've never used a similar language before. It should nevertheless give you a feeling of the language...

What follows is taken directly from the old documentation... Some work might be needed

Functions

Function types are written (x : A) -> B or A -> B for non-dependent functions. Function types can range over arbitrary telescopes, for instance, the type of the substitutivity law for a polymorphic equality

 Eq : (A : Set) -> A -> A -> Set

can be stated as

 (A : Set)(C : A -> Set)(x y : A) -> Eq A x y -> C x -> C y

Functions are constructed by lambda abstractions, which can be either typed or untyped. For instance, both expressions below have type (A : Set) -> A -> A (the second expression checks against other types as well):

 \ (A : Set)(x : A) -> x
 \ A x -> x

more about functions

Implicit arguments

It is possible to omit terms that the type checker can figure out for itself, replacing them by _. If the type checker cannot infer the value of an _ it will report an error. For instance, for the polymorphic identity function

 id : (A : Set) -> A -> A

the first argument can be inferred from the type of the second argument, so we might write id _ zero for the application of the identity function to zero.

Implicit function spaces are written with curly braces instead of parenthesis. For instance,

 _==_  : {A : Set} -> A -> A -> Set
 subst : {A : Set}(C : A -> Set){x y : A} -> x == y -> C x -> C y

Note how the first argument to _==_ is left implicit. Similarly we may leave out the implicit arguments A, x, and y in an application of subst. To give an implicit argument explicitly, enclose in curly braces. The following two expressions are equivalent:

 subst C eq cx
 subst {_} C {_} {_} eq cx

Worth noting is that implicit arguments will be inserted also at the end of an application, so the following is equivalent:

 subst C
 subst C {_} {_}

To get the function of type {x y : A} -> x == y -> C x -> C y we have to explicitly abstract over x and y:

 \{x}{y} -> subst C

Implicit arguments can also be referred to by name, so if we want to give the expression e explicitly for y without giving a value for x we can write

  subst C {y = e} eq cx

When constructing implicit function spaces the implicit argument can be omitted, so both expressions below are valid expressions of type {A : Set} -> A -> A:

  \ {A} x -> x
  \ x -> x

There are no restrictions on when a function space can be implicit. Internally, explicit and implicit function spaces are treated in the same way. This means that there are no guarantees that implicit arguments will be solved. When there are unsolved implicit arguments the type checker will give an error message indicating which application contains the unsolved arguments. The reason for this liberal approach to implicit arguments is that limiting the use of implicit argument to the cases where we guarantee that they are solved rules out many useful cases in practice.

more on implicit arguments

Inductive data types and functions definitions

Functions can be introduced by giving a type and a definition. For instance, the polymorphic identity function can be defined by

 id : {A : Set} -> A -> A
 id x = x

Note that the implicit argument is left out in the left hand side. As in a lambda abstraction it can be given explicitly by enclosing it in curly braces:

 id : {A : Set} -> A -> A
 id {A} x = x

Datatypes are introduced by data declarations. For instance, the natural numbers can be defined by

 data Nat : Set where
   zero : Nat
   suc  : Nat -> Nat

To ensure normalisation, inductive occurrences must appear in strictly positive positions. For instance, the following datatype is not allowed:

 data Bad : Set where
   bad : (Bad -> Bad) -> Bad

since there is a negative occurrence of Bad in the argument to the constructor.

Functions over elements of a datatype can be defined using pattern matching and structural recursion. The addition function on natural numbers is defined by

 _+_ : Nat -> Nat -> Nat
 zero  + m = m
 suc n + m = suc (n + m)

The operator form can be used both in left hand sides and right hand sides as seen here.

Datatypes can be parametrised over a telescope of parameters. These are written after the name of the datatype and scope over the constructors.

 data List (A : Set) : Set where
   []   : List A
   _::_ : A -> List A -> List A

This will introduce the constructors

 []   : {A : Set} -> List A
 _::_ : {A : Set} -> A -> List A -> List A

We can also define families of datatypes. For instance, the family over natural numbers n of proofs that n is even.

 data IsEven : Nat -> Set where
   evenZ  : IsEven zero
   evenSS : (n : Nat) -> IsEven n -> IsEven (suc (suc n))

Note the difference between the left and the right side of the first :. Types appearing on the left are parameters and scope over the constructors. These have to be unchanged in the return types of the constructors. Types appearing on the right are indices which are not in scope in the constructors, and which take on arbitrary values in the constructor return types.

When pattern matching on an element of an inductive family we get information about the index. To distinguish parts of a pattern which are determined by pattern matching (the inaccessible patterns) and the parts which constitutes the actual pattern matching, the inaccessible patterns are prefixed with a dot. In For instance, we can prove that the sum of two even numbers is also even.

 even+ : (n m : Nat) -> IsEven n -> IsEven m -> IsEven (n + m)
 even+ .zero          m  evenZ        em = em
 even+ .(suc (suc n)) m (evenSS n en) em =
     evenSS (n + m) (even+ n m en em)

The proof is by recursion on the proof that n is even. Pattern matching on this proof will force the value of n and hence the patterns for n are prefixed with a dot to indicate that they are not part of the pattern matching. In this case we can make n and m implicit and write the proof as

 even+ : {n m : Nat} -> IsEven n -> IsEven m -> IsEven (n + m)
 even+  evenZ        em = em
 even+ (evenSS n en) em = evenSS _ (even+ en em)

more on inductive data types

Pattern matching

Introduction

This page explains the reduction behaviour of functions defined by pattern matching. Consider the following two implementations of the less than function on natural numbers.

  data Bool : Set where
    true  : Bool
    false : Bool

  data Nat : Set where
    zero : Nat
    suc  : Nat -> Nat

  -- First implementation
  _<1_ : Nat -> Nat -> Bool
  zero  <1 suc m = true
  suc n <1 suc m = n <1 m
  _     <1 zero  = false

  -- Second implementation
  _<2_ : Nat -> Nat -> Bool
  _     <2 zero  = false
  zero  <2 suc m = true
  suc n <2 suc m = n <2 m

Perhaps surprisingly (since the two implementations have the same non-overlapping clauses), _<1_ and _<2_ does not have the same reduction behaviour. For neutral n we have that n <1 zero does not reduce, whereas n <2 zero reduces to false. The reason for this is explained below.

Case tree equivalence

The set of defining equations for a functions are equivalent to a case tree, and the order of the equations determines which one. For _<1_ the equivalent case tree is

n <1 m = case n of
  zero  -> case m of
    suc m -> true
    zero  -> false
  suc n -> case m of
    suc m -> n < m
    zero  -> false

whereas for _<2_ it's

n <2 m = case m of
  zero  -> false
  suc m -> case n of
    zero  -> true
    suc n -> n <2 m

Looking at the case tree it's clear that in the first case n < zero will not reduce to false for neutral n, but in the second case it will. Pattern matching always starts with the left-most constructor pattern in the first clause. For _<1_ this is the first argument, whereas for _<2_ it's the second argument.

Operational view

An alternative more operational way of looking at what's going on is the following. When reducing an application of a function by pattern matching the clauses will be tried one at a time, from top to bottom. For each clause the arguments will be matched against the corresponding patterns from left to right. If there is a mismatch the next clause is tried. If there is an inconclusive match (e.g. matching a neutral term against a constructor pattern) the application won't reduce. For _<1_ we can't see whether n matches zero or not, so the reduction is suspended.

Berry's majority function

The reason for suspending reduction at the first sign of inconclusive matching is to keep the correspondence to a case tree. Consider Berry's majority function:

data Bool : Set where
  tt : Bool
  ff : Bool

maj tt tt tt = tt
maj tt ff x  = x
maj ff x  tt = x
maj x  tt ff = x
maj ff ff ff = F

Here, all clauses are disjoint, but there is no case tree which delivers all equations as definitional equalities. Following the rules for pattern matching given above you'll discover that with this definition you get all but maj x tt ff = x.

more on pattern matching

Page last modified on December 14, 2018, at 06:38 pm
Powered by PmWiki