[Faculty of Science Information and Computing Sciences] 1
Concepts of programming languages Idris an Pali, Stefan Koppier, - - PowerPoint PPT Presentation
Concepts of programming languages Idris an Pali, Stefan Koppier, - - PowerPoint PPT Presentation
[ Faculty of Science Information and Computing Sciences] 1 Concepts of programming languages Idris an Pali, Stefan Koppier, Kevin Namink, Luca Scannapieco and Xander van der Goot [ Faculty of Science Information and Computing Sciences]
[Faculty of Science Information and Computing Sciences] 2
Introduction
Idris is a general purpose pure functional programming language with dependent types. This language supports features that allow it to prove properties of functions. Development of Idris is led by Edwin Brady. Its first stable release was in April 2017.
[Faculty of Science Information and Computing Sciences] 3
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
[Faculty of Science Information and Computing Sciences] 4
Basics
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
Basics
▶ Pure functional ▶ Strict ▶ Syntax, similar to Haskell ▶ Total functions ▶ Dependent types
[Faculty of Science Information and Computing Sciences] 5
Pure functional
Following the definition from “Type driven development with Idris” in a functional programming language:
▶ Programs are composed of functions ▶ Program execution consists of evaluation of functions ▶ Functions are a first-class language construct
And in a pure functional language:
▶ Functions don’t have side effects such as modifying global
variables, throwing exceptions, or preforming console input or
- utput.
▶ As a result, for any specific inputs, a function will always give the
same result.
[Faculty of Science Information and Computing Sciences] 5
Pure functional
Following the definition from “Type driven development with Idris” in a functional programming language:
▶ Programs are composed of functions ▶ Program execution consists of evaluation of functions ▶ Functions are a first-class language construct
And in a pure functional language:
▶ Functions don’t have side effects such as modifying global
variables, throwing exceptions, or preforming console input or
- utput.
▶ As a result, for any specific inputs, a function will always give the
same result.
[Faculty of Science Information and Computing Sciences] 6
Strict
Idris uses strict evaluation by default but has a Lazy keyword. As example the ifThenElse function in the basic library:
ifThenElse : Bool -> (t: Lazy a) -> (e: Lazy a) -> a ifThenElse True t e = t ifThenElse False t e = e
[Faculty of Science Information and Computing Sciences] 7
Types
Types are first class citizens.
StringOrInt : Bool -> Type StringOrInt False -> String StringOrInt True -> Int
[Faculty of Science Information and Computing Sciences] 8
Similarity to other languages
Idris is able to write tactic invocations to prove invariants at compile time, similar to Coq. It is also possible to interactively elaborate with a proof term, similar to Epigram and Agda The syntax of Idris is very similar to Haskell.
[Faculty of Science Information and Computing Sciences] 9
Syntax differences with Haskell
▶ Colon ▶ Type signature mandatory ▶ Omission of “where” in module declarations ▶ Dependent types
Idris:
foo : Nat -> Nat
Haskell:
foo :: Nat -> Nat
[Faculty of Science Information and Computing Sciences] 10
Total functions
A way to prove properties of functions. Idris can distinct between functions that are total and that are partial. Total functions are guaranteed to return a value in finite time for every well-typed input.
▶ Total functions return a value in finite time. ▶ Partial functions return a value if it doesn’t crash or enter an
infinite loop. Examples are:
▶ The append function for finite lists is total. ▶ The first function for a list is partial, it is not defined if the list is
empty.
[Faculty of Science Information and Computing Sciences] 10
Total functions
A way to prove properties of functions. Idris can distinct between functions that are total and that are partial. Total functions are guaranteed to return a value in finite time for every well-typed input.
▶ Total functions return a value in finite time. ▶ Partial functions return a value if it doesn’t crash or enter an
infinite loop. Examples are:
▶ The append function for finite lists is total. ▶ The first function for a list is partial, it is not defined if the list is
empty.
[Faculty of Science Information and Computing Sciences] 11
Dependent types
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
[Faculty of Science Information and Computing Sciences] 12
Dependent types
Idris has dependent types, this means types can depend on other values. The most clear example is the Vect type that is used for lists.
[Faculty of Science Information and Computing Sciences] 13
Dependent types
For example, appending a list of length 3 of strings with a list of length 4
- f strings:
▶ Simple programming languages would use a type AnyList. ▶ Generic programming languages would instead use List String. ▶ But using dependent types would use the types: Vect 3 String
and Vect 4 String. And return a type Vect 7 String.
[Faculty of Science Information and Computing Sciences] 14
Type driven programming
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
Type driven programming
▶ Type ▶ Define ▶ Refine
Interactive editing
[Faculty of Science Information and Computing Sciences] 15
Interactive editing
Type
Defining vectors:
data Vect : Nat -> Type -> Type where Nil : Vect Z a (::) : (x : a) -> (xs : Vect k a) -> Vect (S k) a
[Faculty of Science Information and Computing Sciences] 16
Interactive editing
Define
Append function with vect data type:
app : Vect n a -> Vect m a -> Vect (n + m) a app Nil ys = ?append1 app (x :: xs) ys = ?append2
[Faculty of Science Information and Computing Sciences] 17
Interactive editing
Refine
app : Vect n a -> Vect m a -> Vect (n + m) a app Nil ys = ?append1 app (x :: xs) ys = ?append2
Holes are useful because they help us write functions incrementally. We can check the type of ?append2 and ?append1 to find:
append1 : Vect m a append2 : Vect (S (plus len m)) a
- r search on each of the holes resulting in this:
app : Vect n a -> Vect m a -> Vect (n + m) a app Nil ys = ys app (x :: xs) ys = x :: app xs ys
[Faculty of Science Information and Computing Sciences] 17
Interactive editing
Refine
app : Vect n a -> Vect m a -> Vect (n + m) a app Nil ys = ?append1 app (x :: xs) ys = ?append2
Holes are useful because they help us write functions incrementally. We can check the type of ?append2 and ?append1 to find:
append1 : Vect m a append2 : Vect (S (plus len m)) a
- r search on each of the holes resulting in this:
app : Vect n a -> Vect m a -> Vect (n + m) a app Nil ys = ys app (x :: xs) ys = x :: app xs ys
[Faculty of Science Information and Computing Sciences] 18
What if we can’t make guarantees at compile time?
▶ Dependent pairs
[Faculty of Science Information and Computing Sciences] 19
What if we can’t make guarantees at compile time?
What we’ve seen works for types which we know at compile time. But what if the size of the vectors are not known at compile time? For example, when we want the user to enter elements on the console until the empty string is entered.
readVect : IO (Vect n String) readVect = do x <- getLine if x == "" then return [] else do xs <- readVect return (x :: xs)
But this won’t type check.
[Faculty of Science Information and Computing Sciences] 20
Encapsulate Vect in a different type
data VectUnknown : Type -> Type where MkVect : (n : Nat) -> Vect n a -> VectUnknown a readVect : IO (VectUnknown String) readVect = do x <- getLine if x == "" then return (MkVect 0 []) else do MkVect n xs <- readVect return (MkVect (1 + n) (x :: xs))
Now we can construct a VectUnknown which contains the size. As such, we don’t need to know the size of the vector, which we can use to let the vector type check correctly. But this approach results in a lot of boilerplate code if we need to do this for a lot of different types.
[Faculty of Science Information and Computing Sciences] 20
Encapsulate Vect in a different type
data VectUnknown : Type -> Type where MkVect : (n : Nat) -> Vect n a -> VectUnknown a readVect : IO (VectUnknown String) readVect = do x <- getLine if x == "" then return (MkVect 0 []) else do MkVect n xs <- readVect return (MkVect (1 + n) (x :: xs))
Now we can construct a VectUnknown which contains the size. As such, we don’t need to know the size of the vector, which we can use to let the vector type check correctly. But this approach results in a lot of boilerplate code if we need to do this for a lot of different types.
[Faculty of Science Information and Computing Sciences] 21
Dependent pairs
The solution of this boilerplate code are dependent pairs, which is a more expressive form of a tuple. We can let the type of the second element be computed from the value of the first. For example:
anyVect : (n : Nat ** Vect n String) anyVect = (3 ** ["Rod", "Jane", "Freddy"])
On a side note, we can even omit the explicit size of the vector, as the type system can figure it out for us:
anyVect : (n : Nat ** Vect n String) anyVect = (_ ** ["Rod", "Jane", "Freddy"])
[Faculty of Science Information and Computing Sciences] 21
Dependent pairs
The solution of this boilerplate code are dependent pairs, which is a more expressive form of a tuple. We can let the type of the second element be computed from the value of the first. For example:
anyVect : (n : Nat ** Vect n String) anyVect = (3 ** ["Rod", "Jane", "Freddy"])
On a side note, we can even omit the explicit size of the vector, as the type system can figure it out for us:
anyVect : (n : Nat ** Vect n String) anyVect = (_ ** ["Rod", "Jane", "Freddy"])
[Faculty of Science Information and Computing Sciences] 22
Dependent pair of a number and a vector
Now we can express the same without using an explicitly defined data type.
readVect : IO (n ** Vect n String) readVect = do x <- getLine if x == "" then return (_ ** []) else do (_ ** xs) <- readVect return (_ ** (x :: xs))
[Faculty of Science Information and Computing Sciences] 23
Using types to express properties of data
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
[Faculty of Science Information and Computing Sciences] 24
Using types to express properties of data
Dependent types can be used to express properties of data.
▶ Types can express proofs. ▶ Functions in combination with proofs can be used to express
additional properties. Proofs arise naturally from using dependent types.
[Faculty of Science Information and Computing Sciences] 25
Requiring proofs
We want to check if a vector has the desired length and if so return it
- therwise return Nothing.
A naive implementation could look like this:
exactLength : (len : Nat ) -> (input : Vect m a)
- > Maybe (Vect len a)
exactLength {m} len input = if m == len then Just input else Nothing
Error: Type mismatch between Vect m a (Type of input) and Vect len a (Expected type). Specifically: Type mismatch between m and len
[Faculty of Science Information and Computing Sciences] 25
Requiring proofs
We want to check if a vector has the desired length and if so return it
- therwise return Nothing.
A naive implementation could look like this:
exactLength : (len : Nat ) -> (input : Vect m a)
- > Maybe (Vect len a)
exactLength {m} len input = if m == len then Just input else Nothing
Error: Type mismatch between Vect m a (Type of input) and Vect len a (Expected type). Specifically: Type mismatch between m and len
[Faculty of Science Information and Computing Sciences] 26
Requiring proofs
Idris has no way of knowing whether m and len are equal just by looking at the types. This becomes more apparent if we rewrite it using a case statement:
exactLength : (len : Nat ) -> (input : Vect m a) -> Maybe (Vect len a) exactLength {m} len input = case m == len of True => Just input False => Nothing ▶ Bool does not encode information about the variables m and len in
its type.
[Faculty of Science Information and Computing Sciences] 27
Expressing proofs
Datatype
data EqNat : (num1 : Nat) -> (num2 : Nat) -> Type where Same : (num : Nat) -> EqNat num num
Valid
the (EqNat 3 3) (Same _)
Invalid
the (EqNat 3 4) (Same _)
Type mismatch between EqNat num num (Type of Same num) and EqNat 3 4 (Expected type) Specifically: Type mismatch between 0 and 1
[Faculty of Science Information and Computing Sciences] 28
Equality
To express whether two things are equal you can use the built-in function (=).
data (=) : a -> b -> Type where Refl: x = x ▶ Where refl stands for reflexive, being able to express that a type is
equal to it self.
[Faculty of Science Information and Computing Sciences] 29
Using equality
To replace the comparison we define the function:
checkEqNat : (num1 : Nat) -> (num2 : Nat)
- > Maybe (num1 = num2)
checkEqNat Z Z = Just Refl checkEqNat Z (S k) = Nothing checkEqNat (S k) Z = Nothing checkEqNat (S k) (S j) = case checkEqNat k j of Nothing => Nothing Just prf => Just (cong prf)
Where cong is a helper function for the inductive step:
cong : {func : a -> b} -> x = y -> func x = func y
[Faculty of Science Information and Computing Sciences] 30
Basic implementation
Now we can write exactLength using checkEqNat
exactLength : (len : Nat ) -> (input : Vect m a)
- > Maybe (Vect len a)
exactLength {m} len input = case checkEqNat of Just prf => Just input Nothing => Nothing
[Faculty of Science Information and Computing Sciences] 31
Equality is not trivial
Lets reintroduce the earlier shown append function but with a slight twist: changing the order of m and n.
app : Vect n a -> Vect m a -> Vect (m + n) a app Nil ys = ys app (x :: xs) ys = x :: app xs ys
Error: Type mismatch between Vect (k+1) elem and Vect (S k) elem (Expected type). Operations are not assumed to be associative, commutative, etc. You need to use rewrite rules to introduces those rules.
[Faculty of Science Information and Computing Sciences] 31
Equality is not trivial
Lets reintroduce the earlier shown append function but with a slight twist: changing the order of m and n.
app : Vect n a -> Vect m a -> Vect (m + n) a app Nil ys = ys app (x :: xs) ys = x :: app xs ys
Error: Type mismatch between Vect (k+1) elem and Vect (S k) elem (Expected type). Operations are not assumed to be associative, commutative, etc. You need to use rewrite rules to introduces those rules.
[Faculty of Science Information and Computing Sciences] 32
Empty type
Equality
To express equality we use (=) and Refl.
the (2+2 = 4) (Refl)
Inequality
To express inequality we use the empty type Void.
qm : (2 + 2 = 4 - 1) -> Void qm Refl impossible
[Faculty of Science Information and Computing Sciences] 32
Empty type
Equality
To express equality we use (=) and Refl.
the (2+2 = 4) (Refl)
Inequality
To express inequality we use the empty type Void.
qm : (2 + 2 = 4 - 1) -> Void qm Refl impossible
[Faculty of Science Information and Computing Sciences] 33
Decidability
Definition decidability
A property of some values is decidable if you can always say whether the property holds or not for specific values.
Decidability vs Maybe
Maybe allows you to express whether two values are the same but you can’t say the opposite. For this you need a data type for decidability:
data Dec: (prop : Type) -> Type where Yes : (prf : prop) -> Dec prop No : (contra : prop -> Void) -> Dec prop
[Faculty of Science Information and Computing Sciences] 33
Decidability
Definition decidability
A property of some values is decidable if you can always say whether the property holds or not for specific values.
Decidability vs Maybe
Maybe allows you to express whether two values are the same but you can’t say the opposite. For this you need a data type for decidability:
data Dec: (prop : Type) -> Type where Yes : (prf : prop) -> Dec prop No : (contra : prop -> Void) -> Dec prop
[Faculty of Science Information and Computing Sciences] 34
Expressing contracts in types
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
Expressing contracts in types
▶ Predicates to describe contracts for function inputs and outputs ▶ Example of takeVect
[Faculty of Science Information and Computing Sciences] 35
What do we mean for predicates?
Dependent types like EqNat and =, which you saw before, are used entirely for describing relationships between data. If you can construct a value for a predicate, then you know the property described by that predicate must be true.
[Faculty of Science Information and Computing Sciences] 36
Assumptions and contracts
Who write a function may want to make assumptions on the input.
▶ Remove an element from a vector only under the assumption that
the element is present in the vector
▶ Search for a value in a list only under the assumption that the list
is ordered
▶ Run a database query only under the assumption that the inputs
have been validated They are basically contracts between who write the function and who call it!
[Faculty of Science Information and Computing Sciences] 37
How to use these predicates
Idris allows to express relationships between data in types. So, you can be explicit about the assumptions you’re making about the inputs to a function. The type checker will do the work of checking these assumptions when the function is called.
[Faculty of Science Information and Computing Sciences] 38
Example of takeVect
We want to make a function that, given a vector v and a natural number
n, returns the first n elements of v.
Assumption: n must be smaller or equal than the length of v
[Faculty of Science Information and Computing Sciences] 38
Example of takeVect
We want to make a function that, given a vector v and a natural number
n, returns the first n elements of v.
Assumption: n must be smaller or equal than the length of v
[Faculty of Science Information and Computing Sciences] 39
This is a simple example
By forcing the length of v to be the sum of n and some Nat m.
takeVect : (n: Nat) -> (v: Vect (n+m) a) -> Vect n a takeVect Z xs = [] takeVect (S k) (x :: xs) = x :: takeVect k xs
[Faculty of Science Information and Computing Sciences] 40
Let’s complicate things
Express the assumption as an extra parameter of the function. Define a (dependent) type that describes the relationship between two
Nat, the first smaller or equal than the second. data LorE : Nat -> Nat -> Type where Base : LorE Z n Step : LorE n m -> LorE (S n) (S m)
[Faculty of Science Information and Computing Sciences] 40
Let’s complicate things
Express the assumption as an extra parameter of the function. Define a (dependent) type that describes the relationship between two
Nat, the first smaller or equal than the second. data LorE : Nat -> Nat -> Type where Base : LorE Z n Step : LorE n m -> LorE (S n) (S m)
[Faculty of Science Information and Computing Sciences] 41
Creating a LorE instance
data LorE : Nat -> Nat -> Type where Base : LorE Z n Step : LorE n m -> LorE (S n) (S m)
Creating the proof that 2 is smaller or equal than 4 is possible
prf: LorE 2 4 prf = Step (Step Base)
[Faculty of Science Information and Computing Sciences] 42
Creating a LorE instance
data LorE : Nat -> Nat -> Type where Base : LorE Z n Step : LorE n m -> LorE (S n) (S m)
Creating the proof that 4 is smaller or equal than 2 is impossible
fake_prf: LorE 4 2 fake_prf = Step (Step (Step (Step Base))) Type mismatch between LorE (S n) (S m) (Type of Step _) and LorE 1 0 (Expected type)
[Faculty of Science Information and Computing Sciences] 43
Embedding the proof in the type of the function
takeVect : (m: Nat) -> (v: Vect n a) -> LorE m n -> Vect m a takeVect Z v Base = [] takeVect (S k) (y :: xs) (Step x) = y :: (takeVect k xs x)
[Faculty of Science Information and Computing Sciences] 44
Calling the function
We need to construct the proof that 3 is smaller or equal than 7 and pass it as third parameter of the function
takeVect 3 [1,2,3,4,5,6,7] (Step (Step (Step Base)))
[Faculty of Science Information and Computing Sciences] 45
Providing proofs is painful
The need to provide proofs explicitly like this can add a lot of noise to programs and can harm readability. Idris provides a special kind of implicit argument, marked with the keyword auto, to reduce this noise.
[Faculty of Science Information and Computing Sciences] 46
The auto keyword
takeVect_auto : (m: Nat) -> (v: Vect n a)
- > {auto prf: LorE m n} -> Vect m a
takeVect_auto Z v {prf = Base} = [] takeVect_auto (S k) (y :: xs) {prf = (Step x)} = y :: (takeVect_auto k xs)
Now we can call the function without providing the proof.
takeVect_auto 3 [1,2,3,4,5,6,7]
Idris will look for the proof with the same mechanism it uses for expression search in interactive editing.
[Faculty of Science Information and Computing Sciences] 47
Conclusions
Table of contents
▶ Basics ▶ Dependent types ▶ Type driven programming ▶ Using types to express properties of data ▶ Expressing contracts in types ▶ Conclusions ▶ Questions
[Faculty of Science Information and Computing Sciences] 48
Conclusions
Pro’s:
▶ Familiar syntax, since Haskell is well known. ▶ Produces code with proof of properties. ▶ Dependent types
Con’s:
▶ Still in development, there are missing or not optimized libraries. ▶ Dependent types have a steep learning curve
[Faculty of Science Information and Computing Sciences] 49