Add images to time traveling post.
BIN
content/blog/haskell_lazy_evaluation/fixpoint_1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
content/blog/haskell_lazy_evaluation/fixpoint_2.png
Normal file
After Width: | Height: | Size: 53 KiB |
|
@ -5,6 +5,11 @@ tags: ["Haskell"]
|
|||
draft: true
|
||||
---
|
||||
|
||||
<style>
|
||||
img, figure.small img { max-height: 20rem; }
|
||||
figure.medium img { max-height: 30rem; }
|
||||
</style>
|
||||
|
||||
I recently got to use a very curious Haskell technique
|
||||
{{< sidenote "right" "production-note" "in production:" >}}
|
||||
As production as research code gets, anyway!
|
||||
|
@ -126,7 +131,7 @@ length ((:) 1 [])
|
|||
We're now ready to draw the graph; in this case, it's pretty much identical
|
||||
to the syntax tree of the last form of our expression:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="length_1.png" caption="The initial graph of `length [1]`." >}}
|
||||
|
||||
In this image, the `@` nodes represent function application. The
|
||||
root node is an application of the function `length` to the graph that
|
||||
|
@ -137,7 +142,7 @@ list, and function applications in Haskell are
|
|||
in the process of evaluation, the body of `length` will be reached,
|
||||
and leave us with the following graph:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="length_2.png" caption="The graph of `length [1]` after the body of `length` is expanded." >}}
|
||||
|
||||
Conceptually, we only took one reduction step, and thus, we haven't yet gotten
|
||||
to evaluating the recursive call to `length`. Since `(+)`
|
||||
|
@ -169,15 +174,15 @@ let x = square 5 in x + x
|
|||
|
||||
Here, the initial graph looks as follows:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="square_1.png" caption="The initial graph of `let x = square 5 in x + x`." >}}
|
||||
|
||||
As you can see, this _is_ a graph, not a tree! Since both
|
||||
As you can see, this _is_ a graph, but not a tree! Since both
|
||||
variables `x` refer to the same expression, `square 5`, they
|
||||
are represented by the same subgraph. Then, when we evaluate `square 5`
|
||||
for the first time, and replace its root node with an indirection,
|
||||
we end up with the following:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="square_2.png" caption="The graph of `let x = square 5 in x + x` after `square 5` is reduced." >}}
|
||||
|
||||
There are two `25`s in the tree, and no more `square`s! We only
|
||||
had to evaluate `square 5` exactly once, even though `(+)`
|
||||
|
@ -202,14 +207,14 @@ fix f = let x = f x in x
|
|||
See how the definition of `x` refers to itself? This is what
|
||||
it looks like in graph form:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="fixpoint_1.png" caption="The initial graph of `let x = f x in x`." >}}
|
||||
|
||||
I think it's useful to take a look at how this graph is processed. Let's
|
||||
pick `f = (1:)`. That is, `f` is a function that takes a list,
|
||||
and prepends `1` to it. Then, after constructing the graph of `f x`,
|
||||
we end up with the following:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="fixpoint_2.png" caption="The graph of `fix (1:)` after it's been reduced." >}}
|
||||
|
||||
We see the body of `f`, which is the application of `(:)` first to the
|
||||
constant `1`, and then to `f`'s argument (`x`, in this case). As
|
||||
|
@ -247,11 +252,11 @@ of using application nodes `@`, let's draw an application of a
|
|||
function `f` to arguments `x1` through `xn` as a subgraph with root `f`
|
||||
and children `x`s. The below figure demonstrates what I mean:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="notation.png" caption="The new visual notation used in this section." >}}
|
||||
|
||||
Now, let's write the initial graph for `doRepMax [1,2]`:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_1.png" caption="The initial graph of `doRepMax [1,2]`." >}}
|
||||
|
||||
Other than our new notation, there's nothing too surprising here.
|
||||
At a high level, all we want is the second element of the tuple
|
||||
|
@ -263,7 +268,7 @@ The first step
|
|||
of our hypothetical reduction would replace the application of `doRepMax` with its
|
||||
body, and create our graph's first cycle:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_2.png" caption="The first step of reducing `doRepMax [1,2]`." >}}
|
||||
|
||||
Next, we would do the same for the body of `repMax`. In
|
||||
the following diagram, to avoid drawing a noisy amount of
|
||||
|
@ -273,7 +278,7 @@ edges to similar looking stars. This is merely
|
|||
a visual trick; an edge leading to a little star is
|
||||
actually an edge leading to `fst`. Take a look:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_3.png" caption="The second step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
Since `(,)` is a constructor, let's say that it doesn't
|
||||
need to be evaluated, and that its
|
||||
|
@ -289,9 +294,11 @@ thus replace the application of `snd` with an
|
|||
indirection to this subgraph. This leaves us
|
||||
with the following:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_4.png" caption="The third step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
If our original `doRepMax [1, 2]` expression occured at the top level,
|
||||
Since it's becoming hard to keep track of what part of the graph
|
||||
is actually being evaluated, I marked the former root of `doRepMax [1,2]` with
|
||||
a blue star. If our original expression occured at the top level,
|
||||
the graph reduction would probably stop here. After all,
|
||||
we're evaluating our graphs using call-by-need, and there
|
||||
doesn't seem to be a need for knowing the what the arguments of `(:)` are.
|
||||
|
@ -307,7 +314,7 @@ of `fst`. We evaluate it in a similar manner to `snd`. That is,
|
|||
we replace this `fst` with an indirection to the first element
|
||||
of the argument tuple, which happens to be the subgraph starting with `max`:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_5.png" caption="The fourth step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
Phew! Next, we need to evaluate the body of `max`. Let's make one more
|
||||
simplification here: rather than substitututing `max` for its body
|
||||
|
@ -319,23 +326,23 @@ a call to `fst`, needs to be processed. To do so, we need to
|
|||
evaluate the call to `repMax`. We thus replace `repMax`
|
||||
with its body:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_6.png" caption="The fifth step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
We've reached one of the base cases here, and there
|
||||
are no more calls to `max` or `repMax`. The whole reason
|
||||
we're here is to evaluate the call to `fst` that's one
|
||||
of the arguments to `max`. Given this graph, this is easy.
|
||||
of the arguments to `max`. Given this graph, doing so is easy.
|
||||
We can clearly see that `2` is the first element of the tuple
|
||||
returned by `repMax [2]`. We thus replace `fst` with
|
||||
an indirection to this node:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_7.png" caption="The sixth step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
This concludes our task of evaluating the arguments to `max`.
|
||||
Comparing them, we see that `2` is greater than `1`, and thus,
|
||||
we replace `max` with an indirection to `2`:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_8.png" caption="The seventh step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
The node that we starred in our graph is now an indirection (the
|
||||
one that used to be the call to `fst`) which points to
|
||||
|
@ -351,7 +358,7 @@ which is the call to `snd`. This `snd` is applied to an instance of `(,)`, which
|
|||
can't be reduced any further. Thus, all we have to do is take the second
|
||||
element of the tuple, and replace `snd` with an indirection to it:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_9.png" caption="The eighth step of reducing `doRepMax [1,2]`." class="medium" >}}
|
||||
|
||||
The second element of the tuple was a call to `(:)`, and that's what the mysterious
|
||||
force is processing now. Just like it did before, it starts by looking at the
|
||||
|
@ -362,10 +369,10 @@ Another `2` pops up on the console.
|
|||
Finally, the mysterious force reaches the second argument of the `(:)`,
|
||||
which is the empty list. The empty list also cannot be evaluated any
|
||||
further, so that's what the mysterious force receives. Just like that,
|
||||
there's nothing left to print to the console. The mysterious force ceases,
|
||||
and we're left with the following graph:
|
||||
there's nothing left to print to the console. The mysterious force ceases.
|
||||
After removing the unused nodes, we are left with the following graph:
|
||||
|
||||
{{< todo >}}Add image!{{< /todo >}}
|
||||
{{< figure src="repmax_10.png" caption="The result of reducing `doRepMax [1,2]`." >}}
|
||||
|
||||
As we would have expected, two `2`s are printed to the console.
|
||||
|
BIN
content/blog/haskell_lazy_evaluation/length_1.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
content/blog/haskell_lazy_evaluation/length_2.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
content/blog/haskell_lazy_evaluation/notation.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_1.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_10.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_2.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_3.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_4.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_5.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_6.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_7.png
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_8.png
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
content/blog/haskell_lazy_evaluation/repmax_9.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
content/blog/haskell_lazy_evaluation/square_1.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
content/blog/haskell_lazy_evaluation/square_2.png
Normal file
After Width: | Height: | Size: 45 KiB |