Appendix: Time Travelling with Haskell
Now that you’ve come to this point, it’s time to tell you one secret that only Haskellers are allowed to know: our code can travel in time! To do so, you need to kindly ask the Doctor
for his Tardis Time
Machine
. Gratefully, the machine has a Haskell form: it can be converted into a monad.
The
Tardis
monad is provided by the
tardis
package in Hackage. The interface is similar to a
State
monad, but you can work with state that travels in the normal way, from the current step of execution forward in time, and with state that works backward in time. For each of those states you have a pair of functions.
You move the state forward in time by updating it using
sendFuture
, and you are able to get it at other point in the execution via
getPast
.
The state that travels backward is updated via
sendPast
and obtained via
getFuture
.
In both cases, it’s important to understand that you can obtain only the last version of each state. For example, if you call
sendFuture
twice, any call to
getPast
will retrieve the second version of
sendFuture
.
It’s time to play the trick. Let’s code a function that, given a list of numbers, builds a new list of tuples, with each tuple showing the current sum up to that point from the beginning of the list, and the same but from the end of the list. You can see the result as two states traveling in opposite directions in time: the sum from the beginning goes forward, and the sum from the end travels backward. Figure
A-1
shows this graphically.
Figure A-1Forward and backward states in Tardis
The corresponding Haskell code follows:
{-# LANGUAGE RecursiveDo #-}
import Control.Monad.Tardis
sumListTardis :: [Int] -> [(Int,Int)]
sumListTardis lst = evalTardis (sumListTardis' lst) (0, 0)
sumListTardis' :: [Int] -> Tardis Int Int [(Int,Int)]
sumListTardis' (x:xs) = do
sumFw <-
getPast
let newFw = sumFw + x
sendFuture
$ newFw
rec
let newBw = sumBw + x
sendPast
$ newBw
sumBw <-
getFuture
rest <- sumListTardis' xs
return $ (newFw, newBw):rest
sumListTardis' [] = return []
The forward state handling is straightforward. At each step, you just take the previous state from the past and send to the future a new value that sums the current number to the forward state.
The backward state is much more mysterious. As you can see, the information is sent to the past and then brought from the future. This way of working is needed because if you first send to the past and then try to bring from the future, you would try to get the value of the last call to
getPast
, which is the present moment. This is impossible because the state would depend on itself in a direct way. Instead, the code asks to create a
recursive
do
block, which starts with the
rec
keyword and allows you to refer to a later value.
You can run the Tardis over some list and check that everything works:
*>
sumListTardis [1,2,3,4]
[(1,10),(3,9),(6,7),(10,4)]
While Tardis seems like very weird stuff, with no real applications, there are cases in real life where a computation receives feedback from itself. The archetypical example is in circuits, where an output cable may be connected to an input port again. As in the previous case, in circuits you need to be careful not to create a dependence of a value on itself; you usually add some delay to make the input depend on the output in some previous moment. Another example is bowling, where the points scored after a strike or spare depends on the score in later rounds.
If only you had known that you could travel into the future! Instead of moving step-by-step through examples and exercises, you could have traveled and asked your future self to give you all your Haskell knowledge. But such is the mystery of laziness, of type classes, and of higher-order functions. It’s now time to have fun and explore the rest of the Haskell universe!