.. _sect-typesfuns: =================== Types and Functions =================== Primitive Types --------------- ``Idris`` defines several primitive types: ``Int``, ``Integer`` and ``Float`` for numeric operations, ``Char`` and ``String`` for text manipulation, and ``Ptr`` which represents foreign pointers. There are also several data types declared in the library, including ``Bool``, with values ``True`` and ``False``. We can declare some constants with these types. Enter the following into a file ``prims.idr`` and load it into the ``Idris`` interactive environment by typing ``idris prims.idr``: .. code-block:: idris 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 ``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. The order of definitions is significant — functions and data types must be defined before use. Each definition must have a type declaration, for example see ``x : Int``, ``foo : String``, from the above listing. 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 ``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:`sect-classes` and can be extended to work on user defined types. Boolean expressions can be tested with the ``if...then...else`` construct: :: *prims> if x == 6 * 6 + 6 then "The answer!" else "Not the answer" "The answer!" : String 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: .. code-block:: idris data Nat = Z | 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 (``Z``), or the successor of another natural number (``S k``). Lists can either be empty (``Nil``) or a value added to the front of another list (``x :: xs``). In the declaration for ``List``, we used an infix operator ``::``. New operators such as this can be added using a fixity declaration, as follows: :: infixr 10 :: Functions, data constructors and type constructors may all be given infix operators as names. They may be used in prefix form if enclosed in brackets, e.g. \ ``(::)``. Infix operators can use any of the symbols: :: :+-*/=_.?|&> Nat -> Nat plus Z y = y plus (S k) y = S (plus k y) -- Unary multiplication mult : Nat -> Nat -> Nat mult Z y = Z mult (S k) y = plus y (mult k y) The standard arithmetic operators ``+`` and ``*`` are also overloaded for use by ``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 (``plus`` and ``mult`` above), data constructors (``Z``, ``S``, ``Nil`` and ``::``) and type constructors (``Nat`` and ``List``) are all part of the same namespace. We can test these functions at the ``Idris`` prompt: :: Idris> plus (S (S Z)) (S (S Z)) 4 : Nat Idris> mult (S (S (S Z))) (plus (S (S Z)) (S (S Z))) 12 : Nat **Note:** ``Idris`` automatically desugars the ``Nat`` representation into a more human readable format. The result of ``plus (S (S Z)) (S (S Z))`` is actually ``(S (S (S (S Z))))`` which is the Integer 4. This can be checked at the ``Idris`` prompt: :: Idris> (S (S (S (S Z)))) 4 : 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 4 : Nat Idris> mult 3 (plus 2 2) 12 : 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 ``Nat`` (and similarly structured types) and numbers. This means it can optimise the representation, and functions such as ``plus`` and ``mult``. ``where`` clauses ~~~~~~~~~~~~~~~~~ Functions can also be defined *locally* using ``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: .. code-block:: idris 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 ``where`` block must be indented further than the outer function. **Scope:** Any names which are visible in the outer scope are also visible in the ``where`` clause (unless they have been redefined, such as ``xs`` here). A name which appears only in the type will be in scope in the ``where`` clause if it is a *parameter* to one of the types, i.e. it is fixed across the entire structure. As well as functions, ``where`` blocks can include local data declarations, such as the following where ``MyLT`` is not accessible outside the definition of ``foo``: .. code-block:: idris foo : Int -> Int foo x = case isLT of Yes => x*2 No => x*4 where data MyLT = Yes | No isLT : MyLT isLT = if x < 20 then Yes else No In general, functions defined in a ``where`` clause need a type declaration just like any top level function. However, the type declaration for a function ``f`` *can* be omitted if: - ``f`` appears in the right hand side of the top level definition - The type of ``f`` can be completely determined from its first application So, for example, the following definitions are legal: .. code-block:: idris even : Nat -> Bool even Z = True even (S k) = odd k where odd Z = False odd (S k) = even k test : List Nat test = [c (S 1), c Z, d (S Z)] where c x = 42 + x d y = c (y + 1 + z y) where z w = y + w Dependent Types --------------- Vectors ~~~~~~~ A standard example of a dependent type is the type of “lists with length”, conventionally called vectors in the dependent type literature. They are available as part of the ``Idris`` library, by importing ``Data.Vect``, or we can declare them as follows: .. code-block:: idris data Vect : Nat -> Type -> Type where Nil : Vect Z a (::) : a -> Vect k a -> Vect (S k) a Note that we have used the same constructor names as for ``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 ``Vect`` — it takes a ``Nat`` and a type as an argument, where ``Type`` stands for the type of types. We say that ``Vect`` is *indexed* over ``Nat`` and *parameterised* by ``Type``. Each constructor targets a different part of the family of types. ``Nil`` can only be used to construct vectors with zero length, and ``::`` to construct vectors with non-zero length. In the type of ``::``, we state explicitly that an element of type ``a`` and a tail of type ``Vect k a`` (i.e., a vector of length ``k``) combine to make a vector of length ``S k``. We can define functions on dependent types such as ``Vect`` in the same way as on simple types such as ``List`` and ``Nat`` above, by pattern matching. The type of a function over ``Vect`` will describe what happens to the lengths of the vectors involved. For example, ``++``, defined as follows, appends two ``Vect``\ s: .. code-block:: idris (++) : Vect n a -> Vect m a -> Vect (n + m) a (++) Nil ys = ys (++) (x :: xs) ys = x :: xs ++ ys The type of ``(++)`` 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: .. code-block:: idris (++) : Vect n a -> Vect m a -> Vect (n + m) a (++) Nil ys = ys (++) (x :: xs) ys = x :: xs ++ xs -- BROKEN When run through the ``Idris`` type checker, this results in the following: :: $ idris vbroken.idr --check vbroken.idr:9:23:When elaborating right hand side of Vect.++: When elaborating an application of constructor Vect.::: Can't unify Vect (k + k) a with Vect (plus k m) a Specifically: Can't unify plus k k with plus k m This error message suggests that there is a length mismatch between two vectors — we needed a vector of length ``k + m``, but provided a vector of length ``k + k``. The Finite Sets ~~~~~~~~~~~~~~~ Finite sets, as the name suggests, are sets with a finite number of elements. They are available as part of the ``Idris`` library, by importing ``Data.Fin``, or can be declared as follows: .. code-block:: idris data Fin : Nat -> Type where FZ : Fin (S k) FS : Fin k -> Fin (S k) ``FZ`` is the zeroth element of a finite set with ``S k`` elements; ``FS n`` is the ``n+1``\ th element of a finite set with ``S k`` elements. ``Fin`` is indexed by a ``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 ``Fin Z``. A useful application of the ``Fin`` family is to represent bounded natural numbers. Since the first ``n`` natural numbers form a finite set of ``n`` elements, we can treat ``Fin n`` as the set of natural numbers bounded by ``n``. For example, the following function which looks up an element in a ``Vect``, by a bounded index given as a ``Fin n``, is defined in the prelude: .. code-block:: idris index : Fin n -> Vect n a -> a index FZ (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 (``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 ``Nil`` here. This is because it is impossible. Since there is no element of ``Fin Z``, and the location is a ``Fin n``, then ``n`` can not be ``Z``. As a result, attempting to look up an element in an empty vector would give a compile time type error, since it would force ``n`` to be ``Z``. Implicit Arguments ~~~~~~~~~~~~~~~~~~ Let us take a closer look at the type of ``index``: .. code-block:: idris index : Fin n -> Vect n a -> a It takes two arguments, an element of the finite set of ``n`` elements, and a vector with ``n`` elements of type ``a``. But there are also two names, ``n`` and ``a``, which are not declared explicitly. These are *implicit* arguments to ``index``. We could also write the type of ``index`` as: .. code-block:: idris index : {a:Type} -> {n:Nat} -> Fin n -> Vect n a -> a Implicit arguments, given in braces ``{}`` in the type declaration, are not given in applications of ``index``; their values can be inferred from the types of the ``Fin n`` and ``Vect n a`` arguments. Any name with a 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 ``{a=value}`` and ``{n=value}``, for example: .. code-block:: idris index {a=Int} {n=2} FZ (2 :: 3 :: Nil) In fact, any argument, implicit or explicit, may be given a name. We could have declared the type of ``index`` as: .. code-block:: idris index : (i:Fin n) -> (xs:Vect n a) -> 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. “``using``” notation ~~~~~~~~~~~~~~~~~~~~ Sometimes it is useful to provide types of implicit arguments, particularly where there is a dependency ordering, or where the implicit arguments themselves have dependencies. For example, we may wish to state the types of the implicit arguments in the following definition, which defines a predicate on vectors: .. code-block:: idris data Elem : a -> Vect n a -> Type where Here : {x:a} -> {xs:Vect n a} -> Elem x (x :: xs) There : {x,y:a} -> {xs:Vect n a} -> Elem x xs -> Elem x (y :: xs) An instance of ``Elem x xs`` states that ``x`` is an element of ``xs``. We can construct such a predicate if the required element is ``Here``, at the head of the vector, or ``There``, in the tail of the vector. For example: .. code-block:: idris testVec : Vect 4 Int 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 ``using`` block gives the types and ordering of any implicit arguments which can appear within the block: .. code-block:: idris using (x:a, y:a, xs:Vect n a) data Elem : a -> Vect n a -> Type where Here : Elem x (x :: xs) There : Elem x xs -> Elem x (y :: xs) Note: Declaration Order and ``mutual`` blocks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In general, functions and data types must be defined before use, since dependent types allow functions to appear as part of types, and their reduction behaviour to affect type checking. However, this restriction can be relaxed by using a ``mutual`` block, which allows data types and functions to be defined simultaneously: .. code-block:: idris mutual even : Nat -> Bool even Z = True even (S k) = odd k odd : Nat -> Bool odd Z = False odd (S k) = even k In a ``mutual`` block, first all of the type declarations are added, then the function bodies. As a result, none of the function types can depend on the reduction behaviour of any of the functions in the block. I/O --- 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 ``IO``: .. code-block:: idris data IO a -- IO operation returning a value of type a We’ll leave the definition of ``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 program: .. code-block:: idris main : IO () main = putStrLn "Hello world" The type of ``putStrLn`` explains that it takes a string, and returns an element of the unit type ``()`` via an I/O action. There is a variant ``putStr`` which outputs a string without a newline: .. code-block:: idris putStrLn : String -> IO () putStr : String -> IO () We can also read strings from user input: .. code-block:: idris getLine : IO String A number of other I/O operations are defined in the prelude, for example for reading and writing files, including: .. code-block:: idris 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 “``do``” notation ----------------- I/O programs will typically need to sequence actions, feeding the output of one computation into the input of the next. ``IO`` is an abstract type, however, so we can’t access the result of a computation directly. Instead, we sequence operations with ``do`` notation: .. code-block:: idris greet : IO () greet = do putStr "What is your name? " name <- getLine putStrLn ("Hello " ++ name) The syntax ``x <- iovalue`` executes the I/O operation ``iovalue``, of type ``IO a``, and puts the result, of type ``a`` into the variable ``x``. In this case, ``getLine`` returns an ``IO String``, so ``name`` has type ``String``. Indentation is significant — each statement in the do block must begin in the same column. The ``return`` operation allows us to inject a value directly into an IO operation: .. code-block:: idris return : a -> IO a As we will see later, ``do`` notation is more general than this, and can be overloaded. .. _sect-more-expr: More Expressions ---------------- ``let`` bindings ~~~~~~~~~~~~~~~~ Intermediate values can be calculated using ``let`` bindings: .. code-block:: idris data Person = MkPerson String Int showPerson : Person -> String showPerson p = let MkPerson name age = p in name ++ " is " ++ show age ++ " years old" splitAt : Char -> String -> (String, String) splitAt c x = case break (== c) x of (x, y) => (x, strTail y) We can do simple pattern matching in ``let`` bindings too. For example, we can extract fields from a record as follows, as well as by pattern matching at the top level: .. code-block:: idris data Person = MkPerson String Int showPerson : Person -> String showPerson p = let MkPerson name age = p in name ++ " is " ++ show age ++ " years old" List comprehensions ~~~~~~~~~~~~~~~~~~~ ``Idris`` provides *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 ``expression``, according to the conditions given by the comma separated ``qualifiers``. For example, we can build a list of Pythagorean triples as follows: .. code-block:: idris 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 ``[a..b]`` notation is another shorthand which builds a list of numbers between ``a`` and ``b``. Alternatively ``[a,b..c]`` builds a list of numbers between ``a`` and ``c`` with the increment specified by the difference between ``a`` and ``b``. This works for any numeric type, using the ``count`` function from the prelude. ``case`` expressions ~~~~~~~~~~~~~~~~~~~~ Another way of inspecting intermediate values of *simple* types is to use a ``case`` expression. The following function, for example, splits a string into two at a given character: .. code-block:: idris splitAt : Char -> String -> (String, String) splitAt c x = case break (== c) x of (x, y) => (x, strTail y) ``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 ``case`` expression can match several cases, for example, to inspect an intermediate value of type ``Maybe a``. Recall ``list_lookup`` which looks up an index in a list, returning ``Nothing`` if the index is out of bounds. We can use this to write ``lookup_default``, which looks up an index and returns a default value if the index is out of bounds: .. code-block:: idris 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 : Integer *usefultypes> lookup_default 4 [3,4,5,6] (-1) -1 : Integer **Restrictions:** The ``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 ``let`` and lambda bindings. It will *only* work if: - Each branch *matches* a value of the same type, and *returns* a value of the same type. - The type of the result is “known”. i.e. the type of the expression can be determined *without* type checking the ``case``-expression itself. Dependent Records ----------------- *Records* are data types which collect several values (the record’s *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: .. code-block:: idris record Person : Type where MkPerson : (name : String) -> (age : Int) -> Person fred : Person fred = MkPerson "Fred" 30 Record declarations are like ``data`` declarations, except that they are introduced by the ``record`` keyword, and can only have one constructor. The names of the binders in the constructor type (``name`` and ``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 updated). .. code-block:: bash *record> record { name = "Jim" } fred MkPerson "Jim" 30 : Person *record> record { name = "Jim", age = 20 } fred MkPerson "Jim" 20 : Person The syntax ``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: .. code-block:: idris record Class : Type where ClassInfo : (students : Vect n Person) -> (className : String) -> Class It is safe to update the ``students`` field to a vector of a different length because it will not affect the type of the record: .. code-block:: idris 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 Nested record update ~~~~~~~~~~~~~~~~~~~~ ``Idris`` also provides a convenient syntax for accessing and updating nested records. For example, if a field is accessible with the expression ``c (b (a x))``, it can be updated using the following syntax: .. code-block:: idris record { a->b->c = val } x This returns a new record, with the field accessed by the path ``a->b->c`` set to ``x``. The syntax is first class, i.e. ``record { a->b->c = val }`` itself has a function type. Symmetrically, the field can also be accessed with the following syntax: .. code-block:: idris record { a->b->c } x