SLIDE 1
Verifying Type Class Laws with Equational Reasoning A description - - PowerPoint PPT Presentation
Verifying Type Class Laws with Equational Reasoning A description - - PowerPoint PPT Presentation
Verifying Type Class Laws with Equational Reasoning A description by example Lukas Hofmaier lukas.hofmaier@hsr.ch June 12, 2015 Progam Analysis and Transformation Seminar FS15 Outline Motivation Equational Reasoning Referential Transparency
SLIDE 2
SLIDE 3
Why I’m interested in the topic?
An often touted property of pure functional languages is referential transparency.
SLIDE 4
What is referential transparency?
Computation yields the same value each time it is invoked y = f x g = h y y We can replace the definition of g with g = h (f x) (f x) and get the same result.
SLIDE 5
What is equational reasoning?
Equational reasoning is a method originally used in algebra. It’s the process of proving a given property by substituting equal expressions. (x + a)(x + b) = x2 + (a + b)x + ab
0Example taken from Graham Hutton, Programming in Haskell
SLIDE 6
What is equational reasoning?
Equational reasoning is a method originally used in algebra. It’s the process of proving a given property by substituting equal expressions. (x + a)(x + b) = x2 + (a + b)x + ab (x + a)(x + b) = x2 + ax + xb + ab (use distributivity)
0Example taken from Graham Hutton, Programming in Haskell
SLIDE 7
What is equational reasoning?
Equational reasoning is a method originally used in algebra. It’s the process of proving a given property by substituting equal expressions. (x + a)(x + b) = x2 + (a + b)x + ab (x + a)(x + b) = x2 + ax + xb + ab (use distributivity) x2 + ax + xb + ab = x2 + ax + bx + ab (use commutativity)
0Example taken from Graham Hutton, Programming in Haskell
SLIDE 8
What is equational reasoning?
Equational reasoning is a method originally used in algebra. It’s the process of proving a given property by substituting equal expressions. (x + a)(x + b) = x2 + (a + b)x + ab (x + a)(x + b) = x2 + ax + xb + ab (use distributivity) x2 + ax + xb + ab = x2 + ax + bx + ab (use commutativity) x2 + ax + bx + ab = x2 + (a + b)x + ab (use distributivity)
0Example taken from Graham Hutton, Programming in Haskell
SLIDE 9
Reasoning about programs
We can reason the same way about programs. For instance, we want to verify the following property. length [x] = 1 Given the function definition of length. length [] = 0 length (x:xs) = 1 + length xs
SLIDE 10
Reasoning about programs
Definitions
length [] = 0 length (x:xs) = 1 + length xs
Step-by-step substitution
Begin from the left-hand side length [x] and try to get to the right-hand side 1 by substituting equals-for-equals. length [x]
- - [x] is the same as x:[]
SLIDE 11
Reasoning about programs
Definitions
length [] = 0 length (x:xs) = 1 + length xs
Step-by-step substitution
Begin from the left-hand side length [x] and try to get to the right-hand side 1 by substituting equals-for-equals. length [x]
- - [x] is the same as x:[]
= length (x:[])
- - apply definition
SLIDE 12
Reasoning about programs
Definitions
length [] = 0 length (x:xs) = 1 + length xs
Step-by-step substitution
Begin from the left-hand side length [x] and try to get to the right-hand side 1 by substituting equals-for-equals. length [x]
- - [x] is the same as x:[]
= length (x:[])
- - apply definition
= 1 + length []
- - apply definition
SLIDE 13
Reasoning about programs
Definitions
length [] = 0 length (x:xs) = 1 + length xs
Step-by-step substitution
Begin from the left-hand side length [x] and try to get to the right-hand side 1 by substituting equals-for-equals. length [x]
- - [x] is the same as x:[]
= length (x:[])
- - apply definition
= 1 + length []
- - apply definition
= 1 + 0
- 1 + 0 = 1
= 1
SLIDE 14
Type Classes
Type classes
◮ Describes the behavior of a type. ◮ Type class is a construct that supports overloaded functions
and ad hoc polymorphism
◮ An overloaded function uses different function definitions
depending on the types of the arguments
◮ constrains the type variable in the type signature declaration
- t. t has to be a member of Show.
Example
showlist :: Show t => [t] -> String showlist [] = "" showlist (x:xs) = show x ++ showlist xs
SLIDE 15
Relation of type classes, types and values
type type class value * * 1 *
SLIDE 16
Type class laws
◮ Type classes exhibit type class laws ◮ Laws ensures expected behavior (e.g. associativity) ◮ The Haskell compiler does not enforce type class laws. ◮ We can use laws to apply equational reasoning
SLIDE 17
Relation of type classes with equational reasoning
SLIDE 18
Example Proof for a Monoid Law
Suppose we want to build a plugin system
Plugin
A plugin in our example is an IO action that takes a Char value and does some work with it (e.g. log to a file) It has type Char -> IO ().
Usage
main = do c <- getChar logto c
SLIDE 19
Requirements
Composability
composedPlugin :: Char -> IO () composedPlugin = logto ‘mappend‘ print2stdout
- - mappend :: Plugin -> Plugin -> Plugin
Both arguments are of type Char -> IO (). The return value is also of type Char -> IO ().
Associativity
The order of evaluation must not matter.
composed1 = logto ‘mappend‘ (print2stdout ‘mappend‘ donothing) composed2 = (logto ‘mappend‘ print2stdout) ‘mappend‘ donothing
SLIDE 20
Monoids to the rescue
There is a type class for this behavior.
◮ A monoid is when you have an associative binary function and
a value which acts as an identity. Types which behave like monoids can be part of the type class Monoid. For example list [] under concatenation is a Monoid.
◮ The monoid laws state that mappend must be associative. ◮ If mappend satisfies the monoid laws then we are able to add
plugins without concerning about the order of evaluation.
SLIDE 21
Example Proof for a Monoid Law
Instance implementation
Members of the type class Monoid have to implement the functions mempty and mappend.
instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend
Left identity law
The left identity law states that mempty has to behave like the identity element with respect to mappend.
mappend mempty x = x
SLIDE 22
Proof of the monoid laws
mappend mempty x
- - apply def. mappend
Example from www.haskellforall.com
SLIDE 23
Proof of the monoid laws
mappend mempty x
- - apply def. mappend
= liftA2 mappend mempty x
- - apply def. mempty
Example from www.haskellforall.com
SLIDE 24
Proof of the monoid laws
mappend mempty x
- - apply def. mappend
= liftA2 mappend mempty x
- - apply def. mempty
= liftA2 mappend (pure mempty) x -- apply def. of liftA2
Example from www.haskellforall.com
SLIDE 25
Proof of the monoid laws
mappend mempty x
- - apply def. mappend
= liftA2 mappend mempty x
- - apply def. mempty
= liftA2 mappend (pure mempty) x -- apply def. of liftA2 = (pure mappend <*> pure mempty) <*> x
Example from www.haskellforall.com
SLIDE 26
Proof of the monoid laws
Applicative type class instances satisfy the following laws:
◮ pure f <*> pure x = pure (f x) ◮ pure id <*> x = x
We can use this property to substitute the left-hand side. (pure mappend <*> pure mempty) <*> x
SLIDE 27
Proof of the monoid laws
Applicative type class instances satisfy the following laws:
◮ pure f <*> pure x = pure (f x) ◮ pure id <*> x = x
We can use this property to substitute the left-hand side. (pure mappend <*> pure mempty) <*> x = pure (mappend mempty) <*> x
- - transform to lambda
SLIDE 28
Proof of the monoid laws
Applicative type class instances satisfy the following laws:
◮ pure f <*> pure x = pure (f x) ◮ pure id <*> x = x
We can use this property to substitute the left-hand side. (pure mappend <*> pure mempty) <*> x = pure (mappend mempty) <*> x
- - transform to lambda
= pure (\a -> mappend mempty a) <*> x -- 1. monoid law
SLIDE 29
Proof of the monoid laws
Applicative type class instances satisfy the following laws:
◮ pure f <*> pure x = pure (f x) ◮ pure id <*> x = x
We can use this property to substitute the left-hand side. (pure mappend <*> pure mempty) <*> x = pure (mappend mempty) <*> x
- - transform to lambda
= pure (\a -> mappend mempty a) <*> x -- 1. monoid law = pure (\a -> a) <*> x
- - a -> a = id
SLIDE 30
Proof of the monoid laws
Applicative type class instances satisfy the following laws:
◮ pure f <*> pure x = pure (f x) ◮ pure id <*> x = x
We can use this property to substitute the left-hand side. (pure mappend <*> pure mempty) <*> x = pure (mappend mempty) <*> x
- - transform to lambda
= pure (\a -> mappend mempty a) <*> x -- 1. monoid law = pure (\a -> a) <*> x
- - a -> a = id
= pure id <*> x
- - applicative law
= x
SLIDE 31
Conclusion
◮ Referential transparency makes it possible to conduct
equational reasoning
◮ The process of proving a property is cumbersome and difficult ◮ Equational reasoning provides certainty that a program has
particular properties.
SLIDE 32
For Further Reading
Graham Hutton, Programming in Haskell, Cambridge University Press, 2007. Gabriel Gonzales, Equational reasoning at scale, http://www.haskellforall.com.
SLIDE 33
Why use equational reasoning?
Example Property
Correctness of a program can be specified in the form of an equation. fmap id = id (1) We can use equational reasoning to verify if a property holds.
Goals of verification
◮ Provide certainty that a program has particular properties ◮ Contributes to the reliability and robustness of a software
Examples of use
Example of use
◮ the first formally verified micro kernel, seL4. ◮ streaming library ”pipes”
SLIDE 34
Comparison of testing and proving properties
Suppose we want to check if the following equations holds reverse reverse xs = xs How do we check that?
◮ Testing ◮ Property-based testing ◮ Proof that property holds
SLIDE 35
Testing
In order to check the behavior, a function evaluates both sides of equation and compares the values input = [1,2,3] test_reverse :: Bool test_reverse = reverse (reverse input) == input
SLIDE 36
Comparison of testing and proving properties
Testing Code is compiled and executed. Can expose error. Cannot proof absence of errors. Proof Properties hold under all circumstances.
SLIDE 37
Comparison of testing and proving properties
SLIDE 38
Proof by structural induction
Base case Prove p(0) is true. Induction step Prove p(n + 1) if p(n) (induction hypothesis) is true.
SLIDE 39
Example proof by structural induction
The length of two concatenated lists xs and ys, is the same as the sum of the length of xs and the length of ys length (xs ++ ys) = length xs + length ys
SLIDE 40
Base case
We have to show that the property holds for the base case, the empty list [] length ([] ++ ys) = length [] + length ys
Left-hand side
length ([] ++ ys) = length ys
Right-hand side
length [] + length ys = 0 + length ys = length ys
SLIDE 41
Induction step
We have to show that if the equation holds for any list xs then it also holds for x:xs pplength ([] ++ ys) = length [] + length ys
Left-hand side
length ((x:xs) ++ ys) = length (x:(xs ++ ys)) = 1 + length (xs ++ ys) = 1 + length xs + length ys
Right-hand side
length (x:xs) + length ys 1 + length xs + length ys length [] + length ys
SLIDE 42
An Applicative can be a monoid
SLIDE 43
Reusability of proof
General implementation for mappend. If f is an Applicative and b is a Monoid then f b is also a Monoid {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverlappingInstances #-} instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend
◮ To prove a type class law we can use equational reasoning. ◮ Type classes allow us to generalize definitions. A proof for the
generalization is valid for all specializations.
SLIDE 44
Generalization of Proof
- 1. The standard library provides a Monoid instance for ()
- 2. IO () is a Monoid because IO is part of the Applicative
type class and () is a Monoid. The type (->) r (that’s the type of Haskell functions) is a part of the Applicative type class.
- 3. Char -> IO () is a Monoid because (->) Char is an