Compare commits
6 Commits
18339d7e4d
...
9da584ded4
Author | SHA1 | Date | |
---|---|---|---|
9da584ded4 | |||
9452c90cf3 | |||
a80064f40a | |||
49691803cc | |||
ee4738b245 | |||
b270fa78da |
95
content/blog/haskell_lazy_evaluation.md
Normal file
95
content/blog/haskell_lazy_evaluation.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
title: "Clairvoyance for Good: Using Lazy Evaluation in Haskell"
|
||||
date: 2020-05-03T20:05:29-07:00
|
||||
tags: ["Haskell"]
|
||||
draft: true
|
||||
---
|
||||
|
||||
While tackling a project for work, I ran across a rather unpleasant problem.
|
||||
I don't think it's valuable to go into the specifics here (it's rather
|
||||
large and convoluted); however, the outcome of this experience led me to
|
||||
discover a very interesting technique for lazy functional languages,
|
||||
and I want to share what I learned.
|
||||
|
||||
### Time Traveling
|
||||
Some time ago, I read [this post](https://kcsongor.github.io/time-travel-in-haskell-for-dummies/) by Csongor Kiss about time traveling in Haskell. It's
|
||||
really cool, and makes a lot of sense if you have wrapped your head around
|
||||
lazy evaluation. I'm going to present my take on it here, but please check out
|
||||
Csongor's original post if you are interested.
|
||||
|
||||
Say that you have a list of integers, like `[3,2,6]`. Next, suppose that
|
||||
you want to find the maximum value in the list. You can implement such
|
||||
behavior quite simply with pattern matching:
|
||||
|
||||
```Haskell
|
||||
myMax :: [Int] -> Int
|
||||
myMax [] = error "Being total sucks"
|
||||
myMax (x:xs) = max x $ myMax xs
|
||||
```
|
||||
|
||||
You could even get fancy with a `fold`:
|
||||
|
||||
```Haskell
|
||||
myMax :: [Int] -> Int
|
||||
myMax = foldr1 max
|
||||
```
|
||||
|
||||
All is well, and this is rather elementary Haskell. But now let's look at
|
||||
something that Csongor calls the `repMax` problem:
|
||||
|
||||
> Imagine you had a list, and you wanted to replace all the elements of the
|
||||
> list with the largest element, by only passing the list once.
|
||||
|
||||
How can we possibly do this in one pass? First, we need to find the maximum
|
||||
element, and only then can we have something to replace the other numbers
|
||||
with! It turns out, though, that we can just expect to have the future
|
||||
value, and all will be well. Csongor provides the following example:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
repMax :: [Int] -> Int -> (Int, [Int])
|
||||
repMax [] rep = (rep, [])
|
||||
repMax [x] rep = (x, [rep])
|
||||
repMax (l : ls) rep = (m', rep : ls')
|
||||
where (m, ls') = repMax ls rep
|
||||
m' = max m l
|
||||
|
||||
doRepMax :: [Int] -> [Int]
|
||||
doRepMax xs = xs'
|
||||
where (largest, xs') = repMax xs largest
|
||||
```
|
||||
|
||||
In the above snippet, `repMax` expects to receive the maximum value of
|
||||
its input list. At the same time, it also computes that maximum value,
|
||||
returning it and the newly created list. `doRepMax` is where the magic happens:
|
||||
the `where` clauses receives the maximum number from `repMax`, while at the
|
||||
same time using that maximum number to call `repMax`!
|
||||
|
||||
This works because Haskell's evaluation model is, effectively,
|
||||
[lazy graph reduction](https://en.wikipedia.org/wiki/Graph_reduction). That is,
|
||||
you can think of Haskell as manipulating your code as
|
||||
{{< sidenote "right" "tree-note" "a syntax tree," >}}
|
||||
Why is it called graph reduction, you may be wondering, if the runtime is
|
||||
manipulating syntax trees? To save on work, if a program refers to the
|
||||
same value twice, Haskell has both of those references point to the
|
||||
exact same graph. This violates the tree's property of having only one path
|
||||
from the root to any node, and makes our program a graph. Graphs that
|
||||
refer to themselves also violate the properties of a tree.
|
||||
{{< /sidenote >}} performing
|
||||
substitutions and simplifications as necessary until it reaches a final answer.
|
||||
What the lazy part means is that parts of the syntax tree that are not yet
|
||||
needed to compute the final answer can exist, unsimplied, in the tree. This is
|
||||
what allows us to write the code above: the graph of `repMax xs largest`
|
||||
effectively refers to itself. While traversing the list, it places references
|
||||
to itself in place of each of the elements, and thanks to laziness, these
|
||||
references are not evaluated.
|
||||
|
||||
Let's try a more complicated example. How about instead of creating a new list,
|
||||
we return a `Map` containing the number of times each number occured, but only
|
||||
when those numbers were a factor of the maximum numbers. Our expected output
|
||||
will be:
|
||||
|
||||
```
|
||||
>>> countMaxFactors [1,3,3,9]
|
||||
|
||||
fromList [(1, 1), (3, 2), (9, 1)]
|
||||
```
|
|
@ -40,7 +40,7 @@ $sidenote-highlight-border-width: .2rem;
|
|||
}
|
||||
|
||||
.sidenote-label {
|
||||
border-bottom: .2rem solid $primary-color;
|
||||
border-bottom: .2rem dashed $primary-color;
|
||||
}
|
||||
|
||||
.sidenote-checkbox {
|
||||
|
|
|
@ -14,10 +14,10 @@ h1, h2, h3, h4, h5, h6 {
|
|||
margin-top: .5rem;
|
||||
font-family: $font-heading;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
border-bottom: none;
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
|
@ -37,6 +37,10 @@ pre code {
|
|||
background-color: $code-color;
|
||||
}
|
||||
|
||||
div.highlight table pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
|
@ -69,28 +73,33 @@ pre code {
|
|||
}
|
||||
|
||||
nav {
|
||||
background-color: $primary-color;
|
||||
width: 100%;
|
||||
margin: 1rem 0rem 1rem 0rem;
|
||||
}
|
||||
margin: 0rem 0rem 1rem 0rem;
|
||||
|
||||
nav a {
|
||||
padding: .75em;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
|
||||
transition: color .25s, background-color .25s;
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
background-color: white;
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0.25rem 0.75rem 0.25rem .75rem;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
display: inline-block;
|
||||
border-bottom: none;
|
||||
transition: color .25s;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-subscript {
|
||||
color: #8f8f8f;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
|
@ -122,8 +131,9 @@ h6 {
|
|||
}
|
||||
|
||||
a {
|
||||
color: $primary-color-dark;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border-bottom: .2rem solid $primary-color;
|
||||
}
|
||||
|
||||
img {
|
||||
|
@ -154,6 +164,18 @@ td {
|
|||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
hr.header-divider {
|
||||
background-color: $primary-color;
|
||||
height: 0.3rem;
|
||||
border: none;
|
||||
border-radius: 0.15rem;
|
||||
}
|
||||
|
||||
ul.page-list a {
|
||||
border-bottom: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.katex-html {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{- partial "header.html" . -}}
|
||||
<div class="container"><hr class="header-divider"></div>
|
||||
<main class="container">
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{ define "main" }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
|
||||
<ul>
|
||||
<ul class="page-list">
|
||||
{{ range .Pages.ByDate.Reverse }}
|
||||
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{{ define "main" }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
<div class="post-subscript">
|
||||
<p>Posted on {{ .Date.Format "January 2, 2006" }}.</p>
|
||||
<p>Tags:
|
||||
<p>
|
||||
{{ range .Params.tags }}
|
||||
<a class="button" href="{{ $.Site.BaseURL }}/tags/{{ . | urlize }}">{{ . }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
<p>Posted on {{ .Date.Format "January 2, 2006" }}.</p>
|
||||
</div>
|
||||
|
||||
<div class="post-content">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{{ .Content }}
|
||||
|
||||
Recent posts:
|
||||
<ul>
|
||||
<ul class="page-list">
|
||||
{{ range first 10 (where (where .Site.Pages.ByDate.Reverse "Section" "blog") ".Kind" "!=" "section") }}
|
||||
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#1dc868" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Inconsolata|Lora|Raleway" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inconsolata&family=Raleway&family=Lora&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
|
||||
{{ $style := resources.Get "scss/style.scss" | resources.ToCSS | resources.Minify }}
|
||||
{{ $sidenotes := resources.Get "scss/sidenotes.scss" | resources.ToCSS | resources.Minify }}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{ define "main" }}
|
||||
<h2>Tagged "{{ .Title }}"</h2>
|
||||
|
||||
<ul>
|
||||
<ul class="page-list">
|
||||
{{ range .Pages.ByDate.Reverse }}
|
||||
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<h2>{{ .Title }}</h2>
|
||||
Below is a list of all the tags ever used on this site.
|
||||
|
||||
<ul>
|
||||
<ul class="page-list">
|
||||
{{ range sort .Pages "Title" }}
|
||||
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
|
|
Loading…
Reference in New Issue
Block a user