Skip to content

Commit 47ab0f6

Browse files
committed
Some work on the Eval section
- Add and style tables - Add cross-ref to structural section - Use call-by-name instead of call by name
1 parent eb5d5f8 commit 47ab0f6

File tree

3 files changed

+36
-28
lines changed

3 files changed

+36
-28
lines changed

src/pages/codata/structural.typ

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import "../stdlib.typ": info, warning, solution, exercise
22
== Structural Recursion and Corecursion for Codata
3+
<sec:codata-structural>
34

45

56
In this section we'll build a library for streams, also known as lazy lists. These are the codata equivalent of lists. Whereas a list must have a finite length, streams have an infinite length. We'll use this example to explore structural recursion and structural corecursion as applied to codata.
@@ -582,13 +583,13 @@ twos.take(5)
582583

583584
We get the same result whether we use a method or a `lazy val`, because we are assuming that we are only dealing with pure computations that have no dependency on state that might change. In this case a `lazy val` simply consumes additional space to save on time.
584585

585-
Recomputing a result every time it is needed is known as *call by name*, while caching the result the first time it is computed is known as *call by need*. These two different *evaluation strategies* can be applied to individual values, as we've done here, or across an entire programming. Haskell, for example, uses call by need; all values in Haskell are only computed the first time they are needed. Call by need is also commonly known as *lazy evaluation*. Another alternative, called *call by value*, computes results when they are defined instead of waiting until they are needed. This is the default in Scala.
586+
Recomputing a result every time it is needed is known as *call-by-name*, while caching the result the first time it is computed is known as *call-by-need*. These two different *evaluation strategies* can be applied to individual values, as we've done here, or across an entire programming. Haskell, for example, uses call-by-need; all values in Haskell are only computed the first time they are needed. call-by-need is also commonly known as *lazy evaluation*. Another alternative, called *call-by-value*, computes results when they are defined instead of waiting until they are needed. This is the default in Scala.
586587

587-
We can illustrate the difference between call by name and call by need if we use an impure computation.
588+
We can illustrate the difference between call-by-name and call-by-need if we use an impure computation.
588589
For example, we can define a stream of random numbers.
589590
Random number generators depend on some internal state.
590591

591-
Here's the call by name implementation, using the methods we have already defined.
592+
Here's the call-by-name implementation, using the methods we have already defined.
592593

593594
```scala mdoc:silent
594595
import scala.util.Random
@@ -605,7 +606,7 @@ randoms.take(5)
605606
randoms.take(5)
606607
```
607608

608-
Now let's define the same stream in a call by need style, using `lazy val`.
609+
Now let's define the same stream in a call-by-need style, using `lazy val`.
609610

610611
```scala mdoc:silent
611612
val randomsByNeed: Stream[Double] =
@@ -622,7 +623,7 @@ randomsByNeed.take(5)
622623
randomsByNeed.take(5)
623624
```
624625

625-
If we wanted a stream that had a different random number for each element but those numbers were constant, we could redefine `unfold` to use call by need.
626+
If we wanted a stream that had a different random number for each element but those numbers were constant, we could redefine `unfold` to use call-by-need.
626627

627628
```scala mdoc:silent
628629
def unfoldByNeed[A, B](seed: A, f: A => B, next: A => A): Stream[B] =

src/pages/fps.typ

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
#show raw.where(block: true): set block(fill: rgb("F7F7F7"), inset: 8pt, width: 100%)
1717
#show raw.where(block: true): set text(size: 8pt)
1818

19+
// Table styling
20+
#set table(
21+
inset: 8pt,
22+
stroke: (_, y) => if y >= 0 { (top: 0.8pt) }
23+
)
24+
#show table.cell: set align(start)
25+
#show table.cell.where(y: 0): set text(weight: "bold")
26+
1927

2028
// Front matter
2129
#include "parts/frontmatter.typ"

src/pages/monads/eval.typ

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
<sec:monads:eval>
44

55

6-
[`cats.Eval`][cats.Eval] is a monad that allows us to
7-
abstract over different _models of evaluation_.
8-
We typically talk of two such models: _eager_ and _lazy_,
9-
also called _call-by-value_ and _call-by-name_ respectively.
10-
`Eval` also allows for a result to be _memoized_,
11-
which gives us _call-by-need_ evaluation.
12-
13-
`Eval` is also _stack-safe_,
6+
`cats.Eval` is a monad that allows us to
7+
abstract over different *models of evaluation*.
8+
We first met this concept, also known as evaluation strategies, in @sec:codata-structural.
9+
We typically talk of two such models: *eager* and *lazy*,
10+
also called *call-by-value* and *call-by-name* respectively.
11+
`Eval` also allows for a result to be *memoized*,
12+
which gives us *call-by-need* evaluation.
13+
14+
`Eval` is also *stack-safe*,
1415
which means we can use it in very deep recursions
1516
without blowing up the stack.
1617

1718

1819
=== Eager, Lazy, Memoized, Oh My!
1920

20-
2121
What do these terms for models of evaluation mean?
2222
Let's see some examples.
2323

@@ -26,7 +26,7 @@ We can see the evaluation model using
2626
a computation with a visible side-effect.
2727
In the following example,
2828
the code to compute the value of `x`
29-
happens at place where it is defined
29+
happens at the place where it is defined
3030
rather than on access.
3131
Accessing `x` recalls the stored value
3232
without re-running the code.
@@ -43,7 +43,7 @@ x // second access
4343

4444
This is an example of call-by-value evaluation:
4545

46-
- the computation is evaluated at point where it is defined (eager); and
46+
- the computation is evaluated at the point where it is defined (eager); and
4747
- the computation is evaluated once (memoized).
4848

4949

@@ -100,8 +100,7 @@ The final combination, eager and not memoized, is not possible.
100100

101101

102102
=== Eval's Models of Evaluation
103-
104-
103+
fs
105104
`Eval` has three subtypes: `Now`, `Always`, and `Later`.
106105
They correspond to call-by-value, call-by-name, and call-by-need respectively.
107106
We construct these with three constructor methods,
@@ -172,17 +171,17 @@ z.value // second access
172171
```
173172

174173

175-
The three behaviours are summarized below:
174+
The three behaviours are summarized below.
176175

177-
-----------------------------------------------------------------------
178-
Scala Cats Properties
179-
------------------ ------------------------- --------------------------
180-
`val` `Now` eager, memoized
181-
182-
`def` `Always` lazy, not memoized
183-
184-
`lazy val` `Later` lazy, memoized
185-
------------------ ------------------------- --------------------------
176+
#align(center)[
177+
#table(
178+
columns: (auto, auto, auto),
179+
table.header([*Scala*], [*Cats*], [*Properties*]),
180+
[`val`], [`Now`], [eager, memoized],
181+
[`def`], [`Always`], [lazy, not memoized],
182+
[`lazy val`], [`Later`], [lazy, memoized]
183+
)
184+
]
186185

187186
=== Eval as a Monad
188187

0 commit comments

Comments
 (0)