Advertisement
elvecent

Continuations in Haskell

Nov 18th, 2017
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.30 KB | None | 0 0
  1. The purpose of this text is to guide a reader through the first step towards [grokking](http://www.dictionary.com/browse/grokking) continuations as presented in Haskell (`callCC` not covered). The reader is assumed to have basic knowledge of Haskell including some understanding of monads.
  2.  
  3. ### Abstracting away computations
  4. Suppose we are given a list of lists of integers and we want to compute the sum of squares of products of those lists. This task naturally breaks down into three steps:
  5. 1. Take product of each list of integers and thus form a new list of integers.
  6. 2. In that list, square every integer, again forming a new list of integers.
  7. 3. Summate the resulting list.
  8.  
  9. But to produce this algorithm we need to parse the whole problem and revert it (i. e. start with the last step): the problem's descriptions starts with "sum" and the corresponding algorithm starts with "product". Does it really need to be like this?
  10.  
  11. Let's attempt writing the code directly:
  12. ```haskell
  13. sumSqProd :: [[Int]] -> Int
  14. sumSqProd xss = sum $ map ...
  15. ```
  16.  
  17. Are you tired yet? Do you really want to summate squares of product or do you want to summate absolute values of maximums? Would you like to make a decision later or to make someone else do all the work besides taking the sum? Probably not, but you can! You just need to abstract everything else away:
  18.  
  19. ```haskell
  20. sumWhatever :: [[Int]] -> ([Int] -> Int) -> Int
  21. sumWhatever xss = \f -> sum $ map f xss
  22. ```
  23.  
  24. That's neat. If we are certain about squares and product (and not too lazy), we could finish immediately by providing a function `[Int] -> Int` that does the rest of computation:
  25.  
  26. ```haskell
  27. sqProd :: [Int] -> Int
  28. sqProd xs = square $ product xs
  29. where square x = x * x
  30.  
  31. sumSqProd :: [[Int]] -> Int
  32. sumSqProd xss = sumWhatever xss sqProd
  33. ```
  34.  
  35. > \> sumSqProd [[1,2],[3,4]]
  36. > => 148
  37.  
  38. Fine, but what if we are only certain about squares (or not that eager)? We could abstract taking products too:
  39. ```haskell
  40. sqWhatever :: [Int] -> ([Int] -> Int) -> Int
  41. sqWhatever xs = \f -> square $ f xs
  42. where square x = x * x
  43. ```
  44.  
  45. Now that's a pattern. In fact, this pattern is exactly what we call a continuation.
  46.  
  47. ### Continuations defined
  48.  
  49. At this point you are probably wondering whether it is easier to write an eager explicit definition of our function than to abstract away all the stuff. Because, after all, it is more lines now and in the end we need to glue everything together which looks like pointless additional headache. Well, first of all, Haskell's fine machinery lets us give a gluing mechanism for once and use it later in every instance, so the overhead is not that bad. Secondly, using continuations is a fine approach to modular and asynchronous programming (consider user interfaces).
  50.  
  51. Let's get to business already:
  52. ```haskell
  53. newtype Cont r a = Cont ((a -> r) -> r)
  54.  
  55. runCont :: Cont r a -> (a -> r) -> r
  56. runCont (Cont f) = f
  57.  
  58. sumCont :: [[Int]] -> Cont Int [Int]
  59. sumCont xss = Cont $ \f -> sum $ map f xss
  60.  
  61. sqCont :: [Int] -> Cont Int [Int]
  62. sqCont xs = Cont $ \f -> square $ f xs
  63. where square x = x * x
  64.  
  65. sqProd :: [Int] -> Int
  66. sqProd xs = (runCont $ sqCont xs) product
  67.  
  68. sumSqProd :: [[Int]] -> Int
  69. sumSqProd xss = (runCont $ sumCont xss) sqProd
  70. ```
  71.  
  72. > > sumSqProd [[1,2],[3,4]]
  73. > => 148
  74.  
  75. In practice, you'd want to use continuations defined in some library from Hackage but for now we will write everything in our own fashion.
  76.  
  77. So now our functions with abstracted computations have a type `a -> Cont r b` for some types `a`, `b` and `r`. What we need now is a general way of gluing those functions together, yielding again a similar function.
  78.  
  79. ### Continuations composed
  80.  
  81. You've probably heard about [categories](https://en.wikipedia.org/wiki/Category_theory) before; categories are all about composition. And when we talk about composing funny-valued functions (which is exactly our case now), we probably mean Kleisli categories. Kleisli category is built over some predefined category `C` equiped with a monad `m` and arrows in the Kleisli category are exactly the arrows from category `C` of the form `a -> m b` where `a` and `b` are objects of `C`; composition of said arrows is defined in terms of `C` composition and monadic operations. Our `Cont` type constructor indeed yields a monad (with its first type parameter fixed). So essentially giving an appropriate instance of `Monad` typeclass would give us the desired gluing mechanism for free. But for the sake of clarity, let's take the opposite route: first we will define the composition of funny-valued functions for `Cont` and then we will define monadic operations in that terms.
  82.  
  83. Our task now amounts to writing a function of type `(a -> Cont r b) -> (b -> Cont r c) -> (a -> Cont r c)`. You should get some insight from inspecting that type already: to construct a function from `a` to `Cont r c` while given functions `f :: a -> Cont r b` and `g :: b -> Cont r c`, we need to apply `f` to our `a` value and somehow stick the resulting value of `Cont r b` together with `g`.
  84.  
  85. ```haskell
  86. compK :: (a -> Cont r b)
  87. -> (b -> Cont r c)
  88. -> (a -> Cont r c)
  89. compK f g = \x -> -- take x :: a
  90. Cont $ \h -> -- return a Cont which takes h :: c -> r
  91. (runCont $ f x) $ \y -> (runCont $ g y) h
  92.  
  93. (<==) = compK -- fun notation
  94. ```
  95.  
  96. Constructing a `Cont` value here is just wraping a function `(c -> r) -> r` in `Cont` constructor; so, given `h :: c -> r` we must return `r` value, which can be constructed in the following way:
  97.  
  98. `runCont $ f x` gives us a function `(b -> r) -> r`, and our goal is `r` value;
  99. so we need a function `b -> r`. Now `\y -> (runCont $ g y) h` is exactly such function since `runCont $ g y` gives a vaule of `(c -> r) -> r` and `h :: c -> r`.
  100.  
  101. I know that's lengthy! It might help to write an explicit composition of `sumCont` and `sqCont` in the same manner:
  102.  
  103. ```haskell
  104. sumSqCont :: [[Int]] -> Cont Int [Int]
  105. -- takes sum of squares of somehow folded Int lists
  106. sumSqCont = \xss ->
  107. Cont $ \h ->
  108. (runCont $ sumCont xss) $ \y -> (runCont $ sqCont y) h
  109. ```
  110.  
  111. Now let's see if it actually works:
  112.  
  113. ```haskell
  114. sumSqProd :: [[Int]] -> Int
  115. sumSqProd xss = (runCont $ sumSqCont xss) product
  116. where sumSqCont = (sumCont <== sqCont)
  117. ```
  118.  
  119. > > sumSqProd [[1,2],[3,4]]
  120. > => 148
  121.  
  122. Yay!
  123.  
  124. This can be made even cooler: if we had some procedure to turn `product` into a function `[Int] -> Cont Int Int`, which basically takes a product of a list and then applies some function to resulting value, we could represent `sumSqProd` like this:
  125. ```haskell
  126. sumSqProd = (runCont $ sumCont <== sqCont <== (??? product)) id
  127. ```
  128. where `id` is a function which returns its argument untouched.
  129.  
  130. That function `???` (let's call it `liftCont`) has the type `(a -> b) -> a -> Cont r b`, so given some function `f :: a -> b` and an `a` value it yields a `Cont` value; and that `Cont` value essentialy takes a function `b -> r` and yields `r` value. Well, we can apply `f` to `a` value and send the result into `b -> r` function - and we are done!
  131.  
  132. ```haskell
  133. liftCont :: (a -> b) -> a -> Cont r b
  134. liftCont f = \x -> Cont $ \g -> g $ f x
  135. ```
  136.  
  137. > > (runCont $ liftCont product [1,2,3]) id
  138. > => 6
  139.  
  140. Nice.
  141.  
  142.  
  143. ### Continuation monad
  144.  
  145. Now, to make `Cont` into a monad, we need to supply a `return :: a -> Cont r a` function and a `(>>=) :: Cont r a -> (a -> Cont r b) -> Cont r b` operator.
  146.  
  147. The `return` function trivially takes a value and returns a continuation, so we don't have much choice about what to do with that value - we can only apply an abstracted computation to it:
  148. ```haskell
  149. retCont :: a -> Cont r a
  150. retCont x = Cont $ \f -> f x
  151. ```
  152.  
  153. Then, the definition of `>>=` requires us to somehow turn a `Cont r a` with `a -> Cont r b` into `Cont r b`. Turns out, the composition law in associated Kleisli category can do the actual job, provided that we decorate our values appropriately. To do that, it suffices to turn `Cont r a` into `? -> Cont r a`, take composition and then apply the result to some value of type `?`. We actually don't care about `?` type and its values, so the type `()` will fill this hole just fine:
  154.  
  155. ```haskell
  156. fishCont :: Cont r a -> (a -> Cont r b) -> Cont r b
  157. fishCont c f = (\_ -> c) <== f $ ()
  158. ```
  159.  
  160. Boom! Now `Cont` is a monad.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement