Martin Escardo. Notes originally written for the module Advanced Functional Programming of the University of Birmingham, UK.
Products
We discuss function types of the form A → B (called
non-dependent function types) and of the form (x : A) → B x
(called dependent function types). The latter are also called
products in type theory and this terminology comes from
mathematics.
The identity function
To introduce functions, we useλ-abstractions. For
example, the identity function can be defined as follows:
id : {A : Type} → A → A id = λ x → xBut of course this function can be equivalently written as follows, which we take as our official definition:
id : {A : Type} → A → A id x = x
Logical implication
A logical implication A → B is represented by the
function type A → B, and it is a happy coincidence that
both use the same notation.
In terms of logic, the identity function implements the tautology
“A implies A” when we understand the type
A as a logical proposition. In general, most things we
define in Agda have a dual interpretation type/proposition or
program/proof, like this example.
modus-ponens : {A B : Type} → A → (A → B) → B modus-ponens a f = f a
Modus ponens
is the rule logic that says that if the proposition A holds
and A implies B, then B also
holds. The above definition gives a computational realization of
this.
Dependent functions
As already discussed, a dependent function type
(x : A) → B x, with A : Type and
B : A → Type, is that of functions with input
x in the type A and output in the type
B x. It is called “dependent” because the output
type B x depends on the input element
x : A.
Universal quantification
The logical interpretation of (x : A) → B x is “for all
x : A, B x”. This is because in order to show that this universal
quantification holds, we have to show that B x holds for
each given x:A. The input is the given x : A,
and the output is a justification of B x. We will see some
concrete examples shortly.
Dependent function composition
Composition can be defined for “non-dependent functions”, as in usual mathematics, and, more generally, with dependent functions. With non-dependent functions, it has the following type and definition:_∘_ : {A B C : Type} → (B → C) → (A → B) → (A → C) g ∘ f = λ x → g (f x)
In terms of computation, this means “first do f and then do g”. For
this reason the function composition g ∘ f is often read
“g after f”. In terms of logic, this
implements “If B implies C and also A implies B, then A implies C”.
With dependent types, it has the following more general type but the same definition, which is the one we adopt:
_∘_ : {A B : Type} {C : B → Type} → ((y : B) → C y) → (f : A → B) → (x : A) → C (f x) g ∘ f = λ x → g (f x)
Its computational interpretation is the same, “first do f and then do g”, but its logical understanding changes: “If it is the case that for all y : B we have that C y holds, and we have a function f : A → B, then it is also the case that for all x : A, we have that C (f x) holds”.
Π notation
We have mentioned in the propositions as
types table that the official notation in MLTT for the dependent
function type uses Π, the Greek letter Pi, for
product. We can, if we want, introduce the same notation in
Agda, using a syntax declaration:
Pi : (A : Type) (B : A → Type) → Type Pi A B = (x : A) → B x syntax Pi A (λ x → b) = Π x ꞉ A , b
Important. We are using the alternative symbol
꞉ (typed “\:4” in the emacs mode for Agda),
which is different from the reserved symbol “:”. We will
use the same alternative symbol when we define syntax for the sum type
Σ.
Notice that, for some reason, Agda has this kind of definition
backwards. The end result of this is that now (x : A) → B x
can be written as Π x ꞉ A , B x in Agda as in type theory.
(If you happen to know a bit of maths, you may be familiar with the cartesian
product of a family of sets, and this is the reason the letter
Π is used.)