Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Equational Reasoning in Programming

Equational Reasoning in Programming

Bucharest FP

June 21, 2016
Tweet

More Decks by Bucharest FP

Other Decks in Programming

Transcript

  1. Equational reasoning is the method of manipulating structures such as

    formulas and expressions. The basic idea is that equals can be replaced by equals in any context.
  2. Example f = sum . take 5 . repeat f

    10 = ? f 10 = sum . take 5 . repeat $ 10
  3. Example f = sum . take 5 . repeat f

    10 = ? f 10 = sum . take 5 . repeat $ 10 repeat x = x : repeat x take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs
  4. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10))
  5. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10)) 5 > 0 so we take branch n > 0 of take
  6. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10)) 5 > 0 so we take branch n > 0 of take Pattern match on (x:xs) so, expand repeat first
  7. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10)) 5 > 0 so we take branch n > 0 of take Pattern match on (x:xs) so, expand repeat first
  8. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10)) 5 > 0 so we take branch n > 0 of take Pattern match on (x:xs) so, expand repeat first f 10 = sum (take 5 (10 : repeat 10))
  9. Example (beta reduction) repeat x = x : repeat x

    take n (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (take 5 (repeat 10)) 5 > 0 so we take branch n > 0 of take Pattern match on (x:xs) so, expand repeat first f 10 = sum (take 5 (10 : repeat 10)) f 10 = sum (10 : take 4 (repeat 10))
  10. Example repeat x = x : repeat x take n

    (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (10 : take 4 (repeat 10))
  11. Example repeat x = x : repeat x take n

    (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (10 : take 4 (repeat 10)) Multiple possibilities to evaluate the expression
  12. Example repeat x = x : repeat x take n

    (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (10 : take 4 (repeat 10)) Multiple possibilities to evaluate the expression lazy vs eager evaluation
  13. Diamond theorem Excluding ⊥, all evaluation paths lead to the

    same result. Diamond theorem, Church-Rosser exp1 exp2 exp1 arg fun exp2 fun arg res
  14. Example repeat x = x : repeat x take n

    (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (10 : take 4 (repeat 10))
  15. Example repeat x = x : repeat x take n

    (x:xs) | n > 0 = x : take (n - 1) xs | otherwise = [] sum [] = 0 sum (x:xs) = x + sum xs f 10 = sum (10 : take 4 (repeat 10)) f 10 = sum (10 : take 4 (repeat 10)) f 10 = sum (10 : 10 : take 3 (repeat 10)) f 10 = sum (10 : 10 : 10 : take 2 (repeat 10)) f 10 = sum (10 : 10 : 10 : 10 : take 1 (repeat 10)) f 10 = sum (10 : 10 : 10 : 10 : 10 : take 0 (repeat 10)) f 10 = sum [10, 10, 10, 10, 10] f 10 = 50
  16. Functional programming encourages equational reasoning understanding evaluation / debugging typeclass

    rules GHC rewrite rules refactoring theorem proving optimizations algorithm design data structure design
  17. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging
  18. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging GHCi debugger
  19. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging GHCi debugger hard to use for large programs
  20. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging GHCi debugger hard to use for large programs Hood: Haskell Object Observation Debugger
  21. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging GHCi debugger hard to use for large programs Hood: Haskell Object Observation Debugger print intermediate data structure
  22. Evaluation/Debugging evaluation: see previous example cool tool for teaching hard

    to do for big programs lazy evaluation vs. stack-based debugging GHCi debugger hard to use for large programs Hood: Haskell Object Observation Debugger print intermediate data structure use equational reasoning to reason about state around bug
  23. Typeclass rules class Functor f where fmap :: (a ->

    b) -> f a -> f b fmap id = id fmap (f . g) = fmap f . fmap g
  24. Typeclass rules class (Functor f) => Applicative f where pure

    :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
  25. Typeclass rules class (Functor f) => Applicative f where pure

    :: a -> f a (<*>) :: f (a -> b) -> f a -> f b pure f <*> x = fmap f x pure id <*> v = v pure (.) <*> u <*> v <*> w = u <*> (v <*> w) pure f <*> pure x = pure (f x) u <*> pure y = pure ($ y) <*> u
  26. Typeclass rules class Monoid m where mempty :: m mappend

    :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty (<>) = mappend
  27. Typeclass rules class Monoid m where mempty :: m mappend

    :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty (<>) = mappend mempty <> x = x x <> mempty = x (x <> y) <> z = x <> (y <> z)
  28. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html
  29. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html parametricity makes some rules be always valid (Functor)
  30. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html parametricity makes some rules be always valid (Functor) these rules hold over all instances of typeclass
  31. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html parametricity makes some rules be always valid (Functor) these rules hold over all instances of typeclass same reasoning holds when moving from Maybe a to [a]
  32. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html parametricity makes some rules be always valid (Functor) these rules hold over all instances of typeclass same reasoning holds when moving from Maybe a to [a] allow higher-level reasoning
  33. Typeclass rules these rules are not checked by compiler prove

    them test with QuickCheck or similar http://austinrochford.com/posts/ 2014-05-27-quickcheck-laws.html parametricity makes some rules be always valid (Functor) these rules hold over all instances of typeclass same reasoning holds when moving from Maybe a to [a] allow higher-level reasoning easily change the semantics of the program with minimal impact on performance/accuracy/etc.
  34. GHC rewrite rules {-# RULES "map/map" forall f g xs.

    map f (map g xs) = map (f.g) xs #-}
  35. GHC rewrite rules {-# RULES "map/map" forall f g xs.

    map f (map g xs) = map (f.g) xs #-} map f . map g = map (f . g)
  36. GHC rewrite rules {-# RULES "map/map" forall f g xs.

    map f (map g xs) = map (f.g) xs #-} map f . map g = map (f . g) List fusion function generating list function consuming list to generate result list = temporary data structure why not eliminate it?
  37. List fusion :: Good producers List comprehensions Enumerations of simple

    types Explicit lists The (:) constructor (++)
  38. List fusion :: Good producers List comprehensions Enumerations of simple

    types Explicit lists The (:) constructor (++) map take, drop, filter iterate, repeat zip, zipWith
  39. List fusion :: Good consumers List comprehensions array (++) foldr

    sum, product, all, any map take, drop, filter concat unzip zip, zipWith partition head sortBy
  40. GHC rewrite rules (2) What if some equation is valid

    only for one type? or effective only for some types
  41. GHC rewrite rules (2) What if some equation is valid

    only for one type? or effective only for some types specialize rules
  42. GHC rewrite rules (2) What if some equation is valid

    only for one type? or effective only for some types specialize rules
  43. GHC rewrite rules (2) What if some equation is valid

    only for one type? or effective only for some types specialize rules toDouble :: Real a => a -> Double toDouble = fromRational . toRational
  44. GHC rewrite rules (2) What if some equation is valid

    only for one type? or effective only for some types specialize rules toDouble :: Real a => a -> Double toDouble = fromRational . toRational {-# RULES "toDouble/Int" toDouble = i2d #-} i2d (I# i) = D# (int2Double# i)
  45. Theorem Proving Structural induction. data and codata final algebras and

    final coalgebras sum [] = 0 sum (a:as) = a + sum as
  46. Theorem Proving Structural induction. data and codata final algebras and

    final coalgebras sum [] = 0 sum (a:as) = a + sum as sumSoFar x [] = [x] sumSoFar x (y:ys) = x : sumSoFar (x+y) ys
  47. Data and (Structural) Induction sum [] = 0 sum (a:as)

    = a + sum as sum (map (+1) x) = length x + sum x
  48. Data and (Structural) Induction sum [] = 0 sum (a:as)

    = a + sum as sum (map (+1) x) = length x + sum x Base case:
  49. Data and (Structural) Induction sum [] = 0 sum (a:as)

    = a + sum as sum (map (+1) x) = length x + sum x Base case: [] sum (map (+1) []) = sum [] = 0 length [] + sum [] = 0 + 0 = 0
  50. Data and (Structural) Induction sum [] = 0 sum (a:as)

    = a + sum as sum (map (+1) x) = length x + sum x Base case: [] sum (map (+1) []) = sum [] = 0 length [] + sum [] = 0 + 0 = 0 Inductive case:
  51. Data and (Structural) Induction sum [] = 0 sum (a:as)

    = a + sum as sum (map (+1) x) = length x + sum x Base case: [] sum (map (+1) []) = sum [] = 0 length [] + sum [] = 0 + 0 = 0 Inductive case:(for all constructors) sum (map (+1) (x:xs)) = sum ((x+1) : map (+1) xs) = (x+1) + sum (map (+1) xs) = (x+1) + length xs + sum xs = (1 + length xs) + (x + sum xs) = length (x:xs) + sum (x:xs)
  52. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc)
  53. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc) 1, 1, 2, 3, 5, 8, 13, 21, 34 2, 1, 3, 4, 7, 11, 18, 29, 47
  54. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc) 1, 1, 2, 3, 5, 8, 13, 21, 34 2, 1, 3, 4, 7, 11, 18, 29, 47 Ln+2 = Fn+1 + 2 ∗ Fn
  55. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc) 1, 1, 2, 3, 5, 8, 13, 21, 34 2, 1, 3, 4, 7, 11, 18, 29, 47 Ln+2 = Fn+1 + 2 ∗ Fn luc !! (n+2) = fib !! (n+1) + 2 * fib !! n
  56. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc) 1, 1, 2, 3, 5, 8, 13, 21, 34 2, 1, 3, 4, 7, 11, 18, 29, 47 Ln+2 = Fn+1 + 2 ∗ Fn luc !! (n+2) = fib !! (n+1) + 2 * fib !! n tail (tail luc) = zipWith (+) (tail fib) (map (*2) fib)
  57. Codata and (Structural) Coinduction fib = 1 : 1 :

    zipWith (+) fib (tail fib) luc = 2 : 1 : zipWith (+) luc (tail luc) tail (tail luc) = zipWith (+) (tail fib) (map (*2) fib) zipWith (+) (tail fib) (map (*2) fib) = zipWith (+) (1 : zipWith (+) fib (tail fib)) (2 : 2 : map (*2) (zipWith (+)fib (tail fib))) = 3 : zipWith (+) (zipWith (+) fib (tail fib)) (2 : map (*2) (zipWith (+) fib (tail fib))) = ...
  58. More on codata Firstly, induction principles are well known and

    much used. The coinductive definition and proof principles for coalgebras are less well known by far, and often even not very clearly formulated. Rutten, 2000, seminal paper
  59. More on codata Firstly, induction principles are well known and

    much used. The coinductive definition and proof principles for coalgebras are less well known by far, and often even not very clearly formulated. Rutten, 2000, seminal paper bisimilarity, bisimulation A property holds by induction if there is good reason for it to hold; whereas a property holds by coinduction if there is no good reason for it not to hold. Induction is about finite data, coinduction is about infinite codata.
  60. Solving sudoku type Matrix a = [Row a] type Row

    a = [a] type Grid = Matrix Digit type Digit = Char digits = [’1’..’9’] blank = (== ’0’) solve = filter valid . expand . choices
  61. Solving sudoku type Matrix a = [Row a] type Row

    a = [a] type Grid = Matrix Digit type Digit = Char digits = [’1’..’9’] blank = (== ’0’) solve = filter valid . expand . choices 147 808 829 414 345 923 316 083 210 206 383 297 601
  62. Solving sudoku solve = filter valid . expand . prune

    . choices 12 157 665 459 056 928 801
  63. Solving sudoku solve = filter valid . expand . prune

    . choices 12 157 665 459 056 928 801 After some calculation solve = search . choices search m | not (safe m) = [] | complete m’ = [map (map (head) m’] | otherwise = concat (map search (expand1 m’)) where m’ = prune m
  64. Solving sudoku solve = filter valid . expand . prune

    . choices 12 157 665 459 056 928 801 After some calculation solve = search . choices search m | not (safe m) = [] | complete m’ = [map (map (head) m’] | otherwise = concat (map search (expand1 m’)) where m’ = prune m Blazingly fast: 8s on all puzzles
  65. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible?
  66. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible?
  67. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA
  68. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates
  69. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours
  70. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms
  71. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version
  72. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version compile time: 10s
  73. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version compile time: 10s running time: 550ms
  74. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version compile time: 10s running time: 550ms
  75. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version compile time: 10s running time: 550ms lack of tooling → every calculation must be done by hand
  76. In other languages lazy, functional languages: everything applies eager, functional

    languages: beware ⊥ non-functional languages: possible? C++ code for cypto code on CUDA C++ templates compilation: 24 hours running time: 500ms equational reasoning to translate to non-template version compile time: 10s running time: 550ms lack of tooling → every calculation must be done by hand IDE & automatic refactorings rarely help
  77. Warning Beware of code that is too smart. https://mail.haskell.org/pipermail/haskell-cafe/ 2009-March/058475.html

    different versions of same code different coding styles and levels of Haskell knowledge timings between versions derivation of efficient solution
  78. Warning Beware of code that is too smart. https://mail.haskell.org/pipermail/haskell-cafe/ 2009-March/058475.html

    different versions of same code different coding styles and levels of Haskell knowledge timings between versions derivation of efficient solution and a not-so-trivial bug
  79. Clever Perl code is what you hope you understood in

    the past, when you wrote it; clever Haskell code is what you hope you’ll understand in the future, when you’ll write it yourself! Sjur Gjøstein Karevoll, paraphrased