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
|
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
|
I recently got to use a very curious Haskell technique
|
||||||
{{< sidenote "right" "production-note" "in production:" >}}
|
{{< sidenote "right" "production-note" "in production:" >}}
|
||||||
As production as research code gets, anyway!
|
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
|
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:
|
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
|
In this image, the `@` nodes represent function application. The
|
||||||
root node is an application of the function `length` to the graph that
|
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,
|
in the process of evaluation, the body of `length` will be reached,
|
||||||
and leave us with the following graph:
|
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
|
Conceptually, we only took one reduction step, and thus, we haven't yet gotten
|
||||||
to evaluating the recursive call to `length`. Since `(+)`
|
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:
|
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
|
variables `x` refer to the same expression, `square 5`, they
|
||||||
are represented by the same subgraph. Then, when we evaluate `square 5`
|
are represented by the same subgraph. Then, when we evaluate `square 5`
|
||||||
for the first time, and replace its root node with an indirection,
|
for the first time, and replace its root node with an indirection,
|
||||||
we end up with the following:
|
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
|
There are two `25`s in the tree, and no more `square`s! We only
|
||||||
had to evaluate `square 5` exactly once, even though `(+)`
|
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
|
See how the definition of `x` refers to itself? This is what
|
||||||
it looks like in graph form:
|
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
|
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,
|
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`,
|
and prepends `1` to it. Then, after constructing the graph of `f x`,
|
||||||
we end up with the following:
|
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
|
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
|
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`
|
function `f` to arguments `x1` through `xn` as a subgraph with root `f`
|
||||||
and children `x`s. The below figure demonstrates what I mean:
|
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]`:
|
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.
|
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
|
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
|
of our hypothetical reduction would replace the application of `doRepMax` with its
|
||||||
body, and create our graph's first cycle:
|
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
|
Next, we would do the same for the body of `repMax`. In
|
||||||
the following diagram, to avoid drawing a noisy amount of
|
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
|
a visual trick; an edge leading to a little star is
|
||||||
actually an edge leading to `fst`. Take a look:
|
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
|
Since `(,)` is a constructor, let's say that it doesn't
|
||||||
need to be evaluated, and that its
|
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
|
indirection to this subgraph. This leaves us
|
||||||
with the following:
|
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,
|
the graph reduction would probably stop here. After all,
|
||||||
we're evaluating our graphs using call-by-need, and there
|
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.
|
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
|
we replace this `fst` with an indirection to the first element
|
||||||
of the argument tuple, which happens to be the subgraph starting with `max`:
|
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
|
Phew! Next, we need to evaluate the body of `max`. Let's make one more
|
||||||
simplification here: rather than substitututing `max` for its body
|
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`
|
evaluate the call to `repMax`. We thus replace `repMax`
|
||||||
with its body:
|
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
|
We've reached one of the base cases here, and there
|
||||||
are no more calls to `max` or `repMax`. The whole reason
|
are no more calls to `max` or `repMax`. The whole reason
|
||||||
we're here is to evaluate the call to `fst` that's one
|
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
|
We can clearly see that `2` is the first element of the tuple
|
||||||
returned by `repMax [2]`. We thus replace `fst` with
|
returned by `repMax [2]`. We thus replace `fst` with
|
||||||
an indirection to this node:
|
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`.
|
This concludes our task of evaluating the arguments to `max`.
|
||||||
Comparing them, we see that `2` is greater than `1`, and thus,
|
Comparing them, we see that `2` is greater than `1`, and thus,
|
||||||
we replace `max` with an indirection to `2`:
|
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
|
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
|
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
|
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:
|
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
|
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
|
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 `(:)`,
|
Finally, the mysterious force reaches the second argument of the `(:)`,
|
||||||
which is the empty list. The empty list also cannot be evaluated any
|
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,
|
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,
|
there's nothing left to print to the console. The mysterious force ceases.
|
||||||
and we're left with the following graph:
|
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.
|
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 |