Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
1110 lines (788 sloc) 33 KB
\section{Types and Functions}
\subsection{Primitive Types}
\Idris{} defines several primitive types: \tTC{Int}, \tTC{Integer} and
\tTC{Float} for numeric operations, \tTC{Char} and \tTC{String} for
text manipulation, and \tTC{Ptr} which represents foreign pointers.
There are also several data types declared in the library, including
\tTC{Bool}, with values \tDC{True} and \tDC{False}.
We can declare some constants with these types. Enter the following
into a file \texttt{prims.idr} and load it into the \Idris{} interactive
environment by typing \texttt{idris prims.idr}:
module prims
x : Int
x = 42
foo : String
foo = "Sausage machine"
bar : Char
bar = 'Z'
quux : Bool
quux = False
An \Idris{} file consists of an optional module declaration (here
\texttt{module prims}) followed by an optional list of imports (none here,
however \Idris{} programs can consist of several modules, and the definitions
in each module each have their own namespace, as we will discuss in Section
\ref{sect:namespaces}) and a
collection of declarations and definitions. Each definition must have a type
declaration (here, \texttt{x : Int}, \texttt{foo : String}, etc).
Indentation is significant --- a new declaration begins at the same level
of indentation as the preceding declaration. Alternatively, declarations
may be terminated with a semicolon.
A library module \texttt{prelude} is automatically imported by every \Idris{} program,
including facilities for IO, arithmetic, data structures and various common
functions. The prelude defines several arithmetic and comparison operators,
which we can use at the prompt. Evaluating things at the prompt gives an
answer, and the type of the answer. For example:
*prims> 6*6+6
42 : Int
*prims> x == 6*6+6
True : Bool
All of the usual arithmetic and comparison operators are defined for the primitive
types. They are overloaded using type classes, as we will discuss in Section
\ref{sec:classes} and can be extended to work on user defined types.
Boolean expressions can be tested with the \texttt{if...then...else} construct:
*prims> if x == 6 * 6 + 6 then "The answer!" else "Not the answer"
"The answer!" : String
\subsection{Data Types}
Data types are declared in a similar way to Haskell data types, with a similar
syntax. Natural numbers and lists, for example, can be declared as follows:
data Nat = O | S Nat -- Natural numbers
-- (zero and successor)
data List a = Nil | (::) a (List a) -- Polymorphic lists
The above declarations are taken from the standard library. Unary natural
numbers can be either zero (\texttt{O} - that's a capital letter 'o', not the digit), or
the successor of another natural number (\texttt{S k}).
Lists can either be empty (\texttt{Nil})
or a value added to the front of another list (\texttt{x :: xs}).
In the declaration for \tTC{List}, we used an infix operator \tDC{::}. New operators
such as this can be added using a fixity declaration, as follows:
infixr 10 ::
Functions, data constructors and type constuctors may all be given infix
operators as names. They may be used in prefix form if enclosed in brackets,
e.g. \tDC{(::)}. Infix operators can use any of the symbols:
Functions are implemented by pattern matching, again using a similar syntax to
Haskell. The main difference is that \Idris{} requires type declarations for all
functions, using a single colon \texttt{:} (rather than
Haskell's double colon \texttt{::}). Some natural number arithmetic functions can be
defined as follows, again taken from the standard library:
-- Unary addition
plus : Nat -> Nat -> Nat
plus O y = y
plus (S k) y = S (plus k y)
-- Unary multiplication
mult : Nat -> Nat -> Nat
mult O y = O
mult (S k) y = plus y (mult k y)
The standard arithmetic operators \texttt{+} and \texttt{*} are also overloaded
for use by \texttt{Nat}, and are implemented
using the above functions. Unlike Haskell, there is no restriction on whether
types and function names must begin with a capital letter or not. Function
names (\tFN{plus} and \tFN{mult} above), data constructors (\tDC{O}, \tDC{S},
\tDC{Nil} and \tDC{::}) and type constructors (\tTC{Nat} and \tTC{List}) are
all part of the same namespace.
We can test these functions at the \Idris{} prompt:
Idris> plus (S (S O)) (S (S O))
S (S (S (S O))) : Nat
Idris> mult (S (S (S O))) (plus (S (S O)) (S (S O)))
S (S (S (S (S (S (S (S (S (S (S (S O))))))))))) : Nat
Like arithmetic operations, integer literals are also overloaded using type classes,
meaning that we can also test the functions as follows:
Idris> plus 2 2
S (S (S (S O))) : Nat
Idris> mult 3 (plus 2 2)
S (S (S (S (S (S (S (S (S (S (S (S O))))))))))) : Nat
You may wonder, by the way, why we have unary natural numbers when our
computers have perfectly good integer arithmetic built in. The reason is
primarily that unary numbers have a very convenient structure which is easy to
reason about, and easy to relate to other data structures as we will see later.
Nevertheless, we do not want this convenience to be at the expense of
efficiency. Fortunately, \Idris{} knows about the relationship between
\tTC{Nat} (and similarly structured types) and numbers, so optimises the
representation and functions such as \tFN{plus} and \tFN{mult}.
\subsubsection*{\texttt{where} clauses}
Functions can also be defined \emph{locally} using \texttt{where} clauses. For example,
to define a function which reverses a list, we can use an auxiliary function which
accumulates the new, reversed list, and which does not need to be visible globally:
reverse : List a -> List a
reverse xs = revAcc [] xs where
revAcc : List a -> List a -> List a
revAcc acc [] = acc
revAcc acc (x :: xs) = revAcc (x :: acc) xs
Indentation is significant --- functions in the \texttt{where} block must be indented
further than the outer function.
Any names which are visible in the outer scope are also visible in the \texttt{where}
clause (unless they have been redefined, such as \texttt{xs} here).
\emph{However}, names which appear in the type are \emph{not} in scope. In particular,
in the above example, the \texttt{a} in the top level type and the \texttt{a} in the
auxiliary definifion \texttt{revAcc} are \emph{not} the same. If this is the required
behaviour, the \texttt{a} can be brought into scope as follows:
reverse : List a -> List a
reverse {a} xs = revAcc [] xs where
revAcc : List a -> List a -> List a
\subsection{Dependent Types}
A standard example of a dependent type is the type of ``lists with length'',
conventionally called vectors in the dependent type literature. In \Idris{},
we declare vectors as follows:
data Vect : Set -> Nat -> Set where
Nil : Vect a O
(::) : a -> Vect a k -> Vect a (S k)
Note that we have used the same constructor names as for \tTC{List}. Ad-hoc
name overloading such as this is accepted by \Idris{}, provided that the names
are declared in different namespaces (in practice, normally in different modules).
Ambiguous constructor names can normally be resolved from context.
This declares a family of types, and so the form of the declaration is rather
different from the simple type declarations above. We explicitly state the type
of the type constructor \tTC{Vect} --- it takes a type and a \tTC{Nat} as an
argument, where \tTC{Set} stands for the type of types. We say that \tTC{Vect}
is \emph{parameterised} by a type, and \emph{indexed} over \tTC{Nat}. Each
constructor targets a different part of the family of types. \tDC{Nil} can only
be used to construct vectors with zero length, and \tDC{::} to construct
vectors with non-zero length. In the type of \tDC{::}, we state explicitly that an element
of type \texttt{a} and a tail of type \texttt{Vect a k} (i.e., a vector of length \texttt{k})
combine to make a vector of length \texttt{S k}.
We can define functions on dependent types such as \tTC{Vect} in the same way
as on simple types such as \tTC{List} and \tTC{Nat} above, by pattern matching.
The type of a function over \tTC{Vect} will describe what happens to the
lengths of the vectors involved. For example, \tFN{++}, defined in the
library, appends two \tTC{Vect}s:
(++) : Vect A n -> Vect A m -> Vect A (n + m)
(++) Nil ys = ys
(++) (x :: xs) ys = x :: xs ++ ys
The type of \tFN{(++)} states that the resulting vector's length will be the sum of
the input lengths. If we get the definition wrong in such a way that this does
not hold, \Idris{} will not accept the definition. For example:
(++) : Vect a n -> Vect a m -> Vect a (n + m)
(++) Nil ys = ys
(++) (x :: xs) ys = x :: xs ++ xs -- BROKEN
$ idris vbroken.idr --check
vbroken.idr:3:Can't unify Vect a (S (plus k k)) with Vect a (S (plus k m))
Can't unify k with m
This error message suggests that there is a length mismatch between two vectors
--- we needed a vector of length \texttt{(S (k + m))}, but provided a vector
of length \texttt{(S (k + k))}.
Note that the terms in the error message have been \emph{normalised}, so in
particular \texttt{n + m} has been reduced to \texttt{plus n m}.
\subsubsection{The Finite Sets}
Finite sets, as the name suggests, are sets with a finite number of elements.
They are declared as follows (again, in the prelude):
data Fin : Nat -> Set where
fO : Fin (S k)
fS : Fin k -> Fin (S k)
\tDC{fO} is the zeroth element of a finite set with \texttt{S k} elements;
\texttt{fS n} is the
\texttt{n+1}th element of a finite set with \texttt{S k} elements.
\tTC{Fin} is indexed by a \tTC{Nat}, which
represents the number of elements in the set. Obviously we can't construct an
element of an empty set, so neither constructor targets \texttt{Fin O}.
A useful application of the \tTC{Fin} family is to represent bounded
natural numbers. Since the first \tTC{n} natural numbers form a finite
set of \tTC{n} elements, we can treat \tTC{Fin n} as the set of natural
numbers bounded by \tTC{n}.
For example, the following function which looks up an element in a \tTC{Vect},
by a bounded index given as a \tTC{Fin n}, is defined in the prelude:
index : Fin n -> Vect a n -> a
index fO (x :: xs) = x
index (fS k) (x :: xs) = index k xs
This function looks up a value at a given location in a vector. The location is
bounded by the length of the vector (\texttt{n} in each case), so there is no
need for a run-time bounds check. The type checker guarantees that the location
is no larger than the length of the vector.
Note also that there is no case for \texttt{Nil} here. This is because it is
impossible. Since there is no element of \texttt{Fin O}, and the location is a
\texttt{Fin n}, then \texttt{n} can not be \tDC{O}. As a result, attempting to
look up an element in an empty vector would give a compile time type error,
since it would force \texttt{n} to be \tDC{O}.
\subsubsection{Implicit Arguments}
Let us take a closer look at the type of \texttt{index}:
index : Fin n -> Vect a n -> a
It takes two arguments, an element of the finite set of \texttt{n} elements, and a vector
with \texttt{n} elements of type \texttt{a}. But there are also two names,
\texttt{n} and \texttt{a}, which are not declared explictly. These are \emph{implicit}
arguments to \texttt{index}. We could also write the type of \texttt{index} as:
index : {a:Set} -> {n:Nat} -> Fin n -> Vect a n -> a
Implicit arguments, given in braces \texttt{\{\}} in the type declaration, are not given in
applications of \texttt{index}; their values can be inferred from the types of
the \texttt{Fin n} and \texttt{Vect a n} arguments. Any name which appears as a parameter
or index in a type declaration, but which is otherwise free, will be automatically
bound as an implicit argument.
Implicit arguments can still be given explicitly in applications, using
\texttt{\{a=value\}} and \texttt{\{n=value\}}, for example:
index {a=Int} {n=2} fO (2 :: 3 :: Nil)
In fact, any argument, implicit or explicit, may be given a name. We could have
declared the type of \texttt{index} as:
index : (i:Fin n) -> (xs:Vect a n) -> a
It is a matter of taste whether you want to do this --- sometimes it can help
document a function by making the purpose of an argument more clear.
\subsubsection{``\texttt{using}'' notation}
Sometimes it is necessary to provide types of implicit arguments where
the type checker can not work them out itself. This can happen if there is a
dependency ordering --- obviously, \texttt{a} and \texttt{n} must be given as arguments above
before being used --- or if an implicit argument has a complex type. For example,
we will need to state the types of the implicit arguments in the following
definition, which defines a predicate on vectors:
data Elem : a -> Vect a n -> Set where
here : {x:a} -> {xs:Vect a n} -> Elem x (x :: xs)
there : {x,y:a} -> {xs:Vect a n} -> Elem x xs -> Elem x (y :: xs)
An instance of \texttt{Elem x xs} states that \texttt{x} is an element of
\texttt{xs}. We can construct
such a predicate if the required element is \texttt{here}, at the head of the vector,
or \texttt{there}, in the tail of the vector. For example:
testVec : Vect Int 4
testVec = 3 :: 4 :: 5 :: 6 :: Nil
inVect : Elem 5 testVec
inVect = there (there here)
If the same implicit arguments are being used a lot, it can make a definition
difficult to read. To avoid this problem, a \texttt{using} block gives the types and
ordering of any implicit arguments which can appear within the block:
using (x:a, y:a, xs:Vect a n)
data Elem : a -> Vect a n -> Set where
here : Elem x (x :: xs)
there : Elem x xs -> Elem x (y :: xs)
Computer programs are of little use if they do not interact with the user or
the system in some way. The difficulty in a pure language such as \Idris{} ---
that is, a language where expressions do not have side-effects --- is that I/O
is inherently side-effecting. Therefore in \Idris{}, such interactions are
encapsulated in the type \texttt{IO}:
data IO a -- IO operation returning a value of type a
We'll leave the definition of \texttt{IO} abstract, but effectively it describes what
the I/O operations to be executed are, rather than how to execute them. The
resulting operations are executed externally,
by the run-time system. We've already seen one IO
main : IO ()
main = putStrLn "Hello world"
The type of \texttt{putStrLn} explains that it takes a string, and returns an
element of the unit type () via an I/O action. There is a variant \texttt{putStr} which
outputs a string without a newline:
putStrLn : String -> IO ()
putStr : String -> IO ()
We can also read strings from user input:
getLine : IO String
A number of other I/O operations are defined in the prelude, for example for reading and
writing files, including:
data File -- abstract
data Mode = Read | Write | ReadWrite
openFile : String -> Mode -> IO File
closeFile : File -> IO ()
fread : File -> IO String
fwrite : File -> String -> IO ()
feof : File -> IO Bool
readFile : String -> IO String
\subsection{``\texttt{do}'' notation}
I/O programs will typically need to sequence actions, feeding the output of one
computation into the input of the next. \texttt{IO} is an abstract type, however, so we
can't access the result of a computation directly. Instead, we sequence
operations with \texttt{do} notation:
greet : IO ()
greet = do putStr "What is your name? "
name <- getLine
putStrLn ("Hello " ++ name)
The syntax \texttt{x <- iovalue} executes the I/O operation \texttt{iovalue}, of type
\texttt{IO a}, and
puts the result, of type \texttt{a} into the variable \texttt{x}.
In this case, \texttt{getLine} returns an \texttt{IO String},
so \texttt{name} has type \texttt{String}. Indentation is significant --- each
statement in the do block must begin in the same column.
The \texttt{return} operation allows us to inject a value directly into an IO
return : a -> IO a
As we will see later, \texttt{do} notation is more general than this, and can be
\subsection{Useful Data Types}
\Idris{} includes a number of useful data types and library functions (see the
\texttt{lib/} directory in the distribution). This chapter describes a few of these. The
functions described here are imported automatically by every \Idris{} program, as
part of \texttt{prelude.idr}.
\subsubsection{\texttt{List} and \texttt{Vect}}
We have already seen the \texttt{List} and \texttt{Vect} data types:
data List a = Nil | (::) a (List a)
data Vect : Set -> Nat -> Set where
Nil : Vect a O
(::) : a -> Vect a k -> Vect a (S k)
Note that the constructor names are the same for each --- constructor names (in
fact, names in general) can be overloaded, provided that they are declared in
different namespaces (see Section \ref{sect:namespaces}), and will typically be
resolved according to their type. As syntactic sugar, any type with the constructor
names \texttt{Nil} and \texttt{::} can be written in list form. For example:
\item \texttt{[]} means \texttt{Nil}
\item \texttt{[1,2,3]} means \texttt{1 :: 2 :: 3 :: Nil}
The library also defines a number of functions for manipulating these types.
\texttt{map} is overloaded both for \texttt{List} and \texttt{Vect}
and applies a function to every element of the list or vector.
map : (a -> b) -> List a -> List b
map f [] = []
map f (x :: xs) = f x :: map f xs
map : (a -> b) -> Vect a n -> Vect b n
map f [] = []
map f (x :: xs) = f x :: map f xs
For example, to double every element in a vector of integers:
intVec : Vect Int 5
intVec = [1, 2, 3, 4, 5]
double : Int -> Int
double x = x * 2
You'll find these examples in \texttt{usefultypes.idr} in the \texttt{examples/} directory:
*usefultypes> show (map double intVec)
"[2, 4, 6, 8, 10]" : String
For more details of the functions available on \texttt{List} and \texttt{Vect},
look in the library, in \texttt{prelude/list.idr} and \texttt{prelude/vect.idr} respectively.
Functions include filtering, appending, reversing, and so on. Also remember
that \Idris{} is still in development, so if you don't see the function you
need, please feel free to add it and submit a patch!
\subsubsection*{Aside: Anonymous functions and operator sections}
There are actually neater ways to write the above expression. One way would be
to use an anonymous function:
*usefultypes> show (map (\x => x * 2) intVec)
"[2, 4, 6, 8, 10]" : String
The notation \texttt{$\backslash$x => val} constructs an anonymous function
which takes one argument, \texttt{x} and returns the expression \texttt{val}.
Anonymous functions may take several arguments, separated by commas, e.g.
\texttt{$\backslash$x, y, z => val}. Arguments may also be given explicit
types, e.g. \texttt{$\backslash$x : Int => x * 2}, and can pattern match,
e.g. \texttt{$\backslash$ (x, y) => x + y}.
We could also use an operator section:
*usefultypes> show (map (* 2) intVec)
"[2, 4, 6, 8, 10]" : String
\texttt{(*2)} is shorthand for a function which multiplies a number by 2. It expands to
\texttt{$\backslash$x => x * 2}.
Similarly, \texttt{(2*)} would expand to \texttt{$\backslash$x => 2 * x}.
\texttt{Maybe} describes an optional value. Either there is a value of the given type,
or there isn't:
data Maybe a = Just a | Nothing
\texttt{Maybe} is one way of giving a type to an operation that may fail. For example,
looking something up in a \texttt{List} (rather than a vector) may result in an out of
bounds error:
list_lookup : Nat -> List a -> Maybe a
list_lookup _ Nil = Nothing
list_lookup O (x :: xs) = Just x
list_lookup (S k) (x :: xs) = list_lookup k xs
The \texttt{maybe} function is used to process values of type \texttt{Maybe},
either by applying a function to the value, if there is one, or by providing a default value:
maybe : Maybe a -> |(default:b) -> (a -> b) -> b
The vertical bar $\mid$ before the default value is a laziness annotation. Normally
expressions are evaluated before being passed to a function. This is typically
the most efficient behaviour. However, in this case, the default value might
not be used and if it is a large expression, evaluating it will be wasteful.
The $\mid$ annotation tells the compiler not to evaluate the argument until it is
\subsubsection{Tuples and Dependent Pairs}
Values can be paired with the following built-in data type:
data Pair a b = MkPair a b
As syntactic sugar, we can write \texttt{(a, b)} which, according to context,
means either \texttt{Pair a b} or \texttt{MkPair a b}.
Tuples can contain an arbitrary number of values, represented as nested pairs:
fred : (String, Int)
fred = ("Fred", 42)
jim : (String, Int, String)
jim = ("Jim", 25, "Cambridge")
\subsubsection*{Dependent Pairs}
Dependent pairs allow the type of the second element of a pair to depend on
the value of the first element:
data Exists : (A : Set) -> (P : A -> Set) -> Set where
Ex_intro : {P : A -> Set} -> (a : A) -> P a -> Exists A P
Again, there is syntactic sugar for this. \texttt{(a : A ** P)} is the type of a pair of
A and P, where the name \texttt{a} can occur inside P. \texttt{( a ** p )}
constructs a value of this type. For example, we can pair a number with a
\texttt{Vect} of a particular length.
vec : (n : Nat ** Vect Int n)
vec = (2 ** [3, 4])
The type checker could of course infer the value of the first element from the
length of the vector. We can write an underscore \texttt{\_} in place of values which we
expect the type checker to fill in, so the above definition could also be
written as:
vec : (n : Nat ** Vect Int n)
vec = (_ ** [3, 4])
We might also prefer to omit the type of the first element of the pair, since,
again, it can be inferred:
vec : (n ** Vect Int n)
vec = (_ ** [3, 4])
One use for dependent pairs is to return values of dependent types where the
index is not necessarily known in advance. For example, if we filter elements
out of a \texttt{Vect} according to some predicate, we will not know in advance what the
length of the resulting vector will be:
filter : (a -> Bool) -> Vect a n -> (p ** Vect a p)
If the \texttt{Vect} is empty, the result is easy:
filter p Nil = (_ , [])
In the \texttt{::} case, we need to inspect the result of a recursive call to
\texttt{filter} to
extract the length and the vector from the result. To do this, we use \texttt{with}
notation, which allows pattern matching on intermediate values:
filter p (x :: xs) with (filter p xs)
| ( _ ** xs' ) = if (p x) then ( _ ** x :: xs' ) else ( _ ** xs' )
We will see more on \texttt{with} notation later.
The \texttt{so} data type is a predicate on \texttt{Bool} which guarantees that the
value is true:
data so : Bool -> Set where
oh : so True
This is most useful for providing a static guarantee that a dynamic check has been made.
For example, we might provide a safe interface to a function which draws a pixel
on a graphical display as follows, where \texttt{so (inBounds x y)} guarantees that
the point \texttt{(x,y)} is within the bounds of a 640x480 window:
inBounds : Int -> Int -> Bool
inBounds x y = x >= 0 && x < 640 && y >= 0 && y < 480
drawPoint : (x : Int) -> (y : Int) -> so (inBounds x y) -> IO ()
drawPoint x y oh = unsafeDrawPoint x y
\subsection{More Expressions}
\subsubsection*{\texttt{let} bindings}
Intermediate values can be calculated using \texttt{let} bindings:
mirror : List a -> List a
mirror xs = let xs' = rev xs in
app xs xs'
We can do simple pattern matching in \texttt{let} bindings too. For example, we can extract
fields from a record as follows, as well as by pattern matching at the top level:
data Person = MkPerson String Int
showPerson : Person -> String
showPerson p = let MkPerson name age = p in
name ++ " is " ++ show age ++ " years old"
\subsubsection*{List comprehensions}
\Idris{} provides \emph{comprehension} notation as a convenient shorthand for
building lists. The general form is:
[ expression | qualifiers ]
This generates the list of values produced by evaluating the
\texttt{expression}, according to the conditions given by the comma separated
\texttt{qualifiers}. For example, we can build a list of Pythagorean triples as
pythag : Int -> List (Int, Int, Int)
pythag n = [ (x, y, z) | z <- [1..n], y <- [1..z], x <- [1..y],
x*x + y*y == z*z ]
The \texttt{[a..b]} notation is another shorthand which builds a list of
numbers between \texttt{a} and \texttt{b}. Alternatively \texttt{[a,b..c]}
builds a list of numbers between \texttt{a} and \texttt{c} with the increment
specified by the difference between \texttt{a} and \texttt{c}. This works for
any numeric type, using the \texttt{count} function from the prelude.
\subsubsection*{\texttt{case} expressions}
Another way of inspecting intermediate values of \emph{simple} types
is to use a \texttt{case} expression.
The following function, for example, splits a string into two at a given character:
splitAt : Char -> String -> (String, String)
splitAt c x = case break (== c) x of
(x, y) => (x, strTail y)
\texttt{break} is a library function which breaks a string into a pair of strings
at the point where the given function returns true. We then deconstruct the
pair it returns, and remove the first character of the second string.
A \texttt{case} expression can match several cases, for example, to inspect an
intermediate value of type \texttt{Maybe a}. Recall \texttt{list\_lookup} which
looks up an index in a list, returning \texttt{Nothing} if the index is out
of bounds. We can use this to write \texttt{lookup\_default}, which
looks up an index and returns a default value if the index is out of bounds:
lookup_default : Nat -> List a -> a -> a
lookup_default i xs def = case list_lookup i xs of
Nothing => def
Just x => x
If the index is in bounds, we get the value at that index, otherwise we get
a default value:
*usefultypes> lookup_default 2 [3,4,5,6] (-1)
5 : Int
*usefultypes> lookup_default 4 [3,4,5,6] (-1)
-1 : Int
\textbf{Restrictions:} The \texttt{case} construct is intended for simple analysis
of intermediate expressions to avoid the need to write auxiliary functions, and is
also used internally to implement pattern matching \texttt{let} and lambda bindings.
It will \emph{only} work if:
\item Each branch \emph{matches} a value of the same type, and \emph{returns} a
value of the same type.
\item The type of the result is ``known''. i.e. the type of the expression can be
determined \emph{without} type checking the \texttt{case}-expression itself.
\subsection{Dependent Records}
\remph{Records} are data types which collect several values (the record's
\remph{fields}) together. \Idris{} provides syntax for defining records and
automatically generating field access and update functions. For example, we
can represent a person's name and age in a record:
record Person : Set where
MkPerson : (name : String) ->
(age : Int) -> Person
fred : Person
fred = MkPerson "Fred" 30
Record declarations are like \texttt{data} declarations, except that they are
introduced by the \texttt{record} keyword, and can only have one constructor.
The names of the binders in the constructor type (\texttt{name} and \texttt{age})
here are the field names, which we can use to access the field values:
*record> name fred
"Fred" : String
*record> age fred
30 : Int
*record> :t name
name : Person -> String
We can also use the field names to update a record (or, more precisely, produce a
new record with the given fields updates).
*record> record { name = "Jim" } fred
MkPerson "Jim" 30 : Person
*record> record { name = "Jim", age = 20 } fred
MkPerson "Jim" 20 : Person
The syntax \texttt{record \{ field = val, ... \}} generates a function which updates
the given fields in a record.
Records, and fields within records, can have dependent types.
Updates are allowed to change the type of a field,
provided that the result is well-typed, and the result does not affect the type of
the record as a whole. For example:
record Class : Set where
ClassInfo : (students : Vect Person n) ->
(className : String) ->
It is safe to update the \texttt{students} field to a vector of a different length
because it will not affect the type of the record:
addStudent : Person -> Class -> Class
addStudent p c = record { students = p :: students c } c
*record> addStudent fred (ClassInfo [] "CS")
ClassInfo (prelude.vect.:: (MkPerson "Fred" 30) (prelude.vect.Nil)) "CS"
: Class
Jump to Line
Something went wrong with that request. Please try again.