An overview of the Idris functional programming language. Idris has a number of interesting features such as dependent types, function totality checking and theorem proving.
2. Who am I?
● Developer for ~10 years
● Java/Clojure/Python/Node/XQuery
● Contractor with Equal Experts
● Soft spot for esolangs
3. Who am I?
Unfortunately, I:
● Don’t know Haskell very well!
● Don’t have a Computer Science/Maths background!
Luckily, you don’t need either.
4. Making Software. Better.
Simple solutions to big business problems.
Equal Experts is a network of talented, experienced, software
consultants, specialising in agile delivery.
7. What is Idris?
● Pure functional language with dependent types
● Compiled, eagerly evaluated
● Totality checking
● Theorem proving
● REPL + compiler interactive editing support
● Almost-but-not-quite Haskell syntax
8. Where can I get Idris?
https://www.idris-lang.org/
Mac: brew install idris
Linux: Install Cabal, then cabal install idris
Windows: Prebuilt binaries (or more esoteric methods)
11. Compiler Editor Support
Idris’s compiler helps out when you’re writing code...
● Clause creation
● Case splitting
● Type checking
● Proof search
● & more...
Provided you’re using one of Atom/Emacs/Sublime/Vim!
12. Compiler Editor Support
myPlus : Nat -> Nat -> Nat
As is traditional, let’s reimplement a library function!
Nat is a type that represents a natural (i.e. non-negative)
number.
The function myPlus takes two Nats and returns a Nat.
14. Compiler Editor Support
myPlus : Nat -> Nat -> Nat
myPlus k j : ?myPlusRhs
The bit with a ? is called a ‘hole’ - it signifies something to be
filled in later.
This lets us write functions incrementally.
16. Compiler Editor Support
myPlus : Nat -> Nat -> Nat
myPlus Z j : ?myPlusRhs1
myPlus (S k) j : ?myPlusRhs2
Obvious proof search (o) on ?myPlusRhs1 gives...
17. Compiler Editor Support
myPlus : Nat -> Nat -> Nat
myPlus Z j : j
myPlus (S k) j : ?myPlusRhs2
Leaving only the last bit to fill in...
18. Compiler Editor Support
myPlus : Nat -> Nat -> Nat
myPlus Z j : j
myPlus (S k) j : S (myPlus k j)
Done! We can also verify the function is total.
Only total functions will be evaluated during type checking.
19. Total functions
To be total, a function f must:
● Cover all possible inputs
● Be well-founded
● Not use any data types which are not strictly positive
● Not call any non-total functions
20. Dependent types
Types in Idris are first-class - a function can be passed and can
return a type.
isSingleton : Bool -> Type
isSingleton True = Nat
isSingleton False = List Nat
mkSingle : (x : Bool) -> isSingleton x
mkSingle True = 0
mkSingle False = []
21. Dependent types
Vect represents a list with a Nat length.
Vect lets us express stronger guarantees about the inputs and
outputs to a function.
myReverse : List t -> List t
myReverse xs = []
Clearly this won’t do what it should, but it does compile!
23. Records
Records collect several types (the record’s fields) together.
record Person where
constructor MkPerson
firstName, lastName : String
age : Int
batman : Person
batman = MkPerson "Bruce" "Wayne" 52
27. Interfaces
An interface defines a function usable across multiple types,
similar to Haskell type classes.
interface Show a where
show : a -> String
This will generate the method:
show : Show a => a -> String
28. Theorem proving
Like Agda and Coq, Idris can be used to prove theorems. Let’s
try to prove something easy!
0 + x = x
All we need is to create a program with a precise enough type,
thanks to the Curry-Howard correspondence.
Disclaimer: I am not a mathematician!
29. Theorem proving
Expressing this in Idris:
plusReduces : (n: Nat) -> plus Z n = n
Asking the compiler to create a clause (d) gives us...
30. Theorem proving
plusReduces : (n: Nat) -> plus Z n = n
plusReduces n = ?plusReduces_rhs
Performing a proof search (o) gives us...
31. Theorem proving
plusReduces : (n: Nat) -> plus Z n = n
plusReduces n = Refl
Refl (reflexive) indicates that a value is equal to itself.
We can only return Refl if the values actually are equal!
33. Theorem proving
To express that a proposition is impossible:
nineteeneightyfour : 2 + 2 = 5 -> Void
nineteeneightyfour prf = ?rhs
The Void type indicates a type which cannot be constructed.
Case split (c) on prf and we get...
34. Theorem proving
nineteeneightyfour : 2 + 2 = 5 -> Void
nineteeneightyfour Refl impossible
impossible tells Idris that the value cannot be constructed.
35. Theorem proving
We should check that a function that returns Void is a total
function!
Otherwise, you could write a function that claims to return
Void by looping forever:
loop : Void
loop = loop
36. Theorem proving
What if we want to prove the same theorem about addition
reduction, with the arguments reversed?
x + 0 = x
Shouldn’t this be the same proof?
Unfortunately, the proof depends on Idris’ implementation of
plus.
37. Theorem proving
plusReduces : (n: Nat) -> plus n Z = n
plusReduces Z = Refl
plusReduces (S k) = ?plusReduces_rhs
The type of ?plusReduces_rhs is S (plus k 0) = S k.
We know we need to use plusReduces k so we can recurse
down to the base case. Let’s refine by using a hole.
38. Theorem proving
plusReduces : (n: Nat) -> plus n Z = n
plusReduces Z = Refl
plusReduces (S k) = ?prf (plusReduces k)
The type of ?prf is (plus k 0 = k) -> S (plus k 0) = S k.
In other words, we need to tell Idris that applying S doesn’t
change the truth of the proof.
39. Theorem proving
plusReduces : (n: Nat) -> plus n Z = n
plusReduces Z = Refl
plusReduces (S k) = cong (plusReduces k)
cong (congruence) is a library function which states that
equality respects function application:
cong : {f : t -> u} -> a = b -> f a = f b
40. Dependent Types Redux
It should be easy to remove an element from a Vect...right?
removeElem : DecEq a => (value : a) -> (xs : Vect (S n) a) -> Vect n a
DecEq is an interface which allows you to state two values are
equal.
41. Dependent Types Redux
Taking a naive approach, we eventually get:
removeElem : DecEq a => (value : a) -> (xs : Vect (S n) a) -> Vect n a
removeElem value (x :: xs) = case decEq value x of
Yes prf => xs
No contra => x :: removeElem value xs
But this won’t compile! We’ve said that the Vect will be
non-empty (S n) but we may generate an empty Vect if we
don’t find the element to remove.
42. Dependent Types Redux
What we need is a proof that the value to remove is actually in
the Vect.
The built-in Elem type gives us this. Its signature is:
data Elem : a -> Vect k a -> Type where
Here : Elem x (x :: xs)
There : (later : Elem x xs) -> Elem x (y :: xs)
43. Dependent Types Redux
Let’s try again, with an Elem as proof.
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value xs prf = ?removeElem_rhs
Case split on prf...
44. Dependent Types Redux
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ?removeElem_rhs1
removeElem value (y :: ys) (There later) = ?removeElem_rhs2
The first case is trivial, the second less so.
45. Dependent Types Redux
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ys
removeElem value (y :: ys) (There later) = ?removeElem_rhs2
prf proves that the value exists in ys and so must have a
nonzero length.
46. Dependent Types Redux
Bringing the implicit variable n into scope and case splitting on
it:
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ys
removeElem {n = Z} value (y :: ys) (There later) = ?removeElem_rhs1
removeElem {n = (S k)} value (y :: ys) (There later) = ?removeElem_rhs2
47. Dependent Types Redux
We know ys has a nonzero length in the second hole:
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ys
removeElem {n = Z} value (y :: ys) (There later) = ?removeElem_rhs1
removeElem {n = (S k)} value (y :: ys) (There later)
= y :: removeElem value ys later
48. Dependent Types Redux
The only remaining case has a proof that the value is present in
an empty Vect, which is clearly contradictory.
We can tell Idris this is so using absurd.
This asserts that its input is of the Uninhabited interface, i.e.
it’s a type with no values.
49. Dependent Types Redux
removeElem : (value : a) ->
(xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ys
removeElem {n = Z} value (y :: ys) (There later) = absurd later
removeElem {n = (S k)} value (y :: ys) (There later)
= y :: removeElem value ys later
Luckily, you can define arguments as auto to let Idris try to
find them by itself (so you don’t have to prove everything!)