|
|
|
@ -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. |
|
|
|
|
|