summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdoardo La Greca2025-08-22 21:30:12 +0200
committerEdoardo La Greca2025-08-22 21:30:12 +0200
commit62396e4ed3cd37ccf8ec30e53913a494ab8dbdd0 (patch)
treed2342c9b4487d389f39ffe431430f24a5e98cd7d
parentb75db22a1464e3b1d2f40b1e212f9826d3e1c46c (diff)
add partial notes for lecture 6 (up to the "Consequences" section)
-rw-r--r--lec06/notes.md48
1 files changed, 48 insertions, 0 deletions
diff --git a/lec06/notes.md b/lec06/notes.md
new file mode 100644
index 0000000..85f5e7d
--- /dev/null
+++ b/lec06/notes.md
@@ -0,0 +1,48 @@
+# Lecture 6: Laziness
+
+## Strict evaluation
+
+Strict evaluation is the conceptual opposite of lazy evaluation. Under strict evaluation, function arguments are evaluated *before* evaluating the overlying function.
+
+The difference between these two kinds of evaluation, in particular the reason for considering lazy over strict, is that it only calculates what's necessary in order to obtain the final result. As an example, if a function was defined as having two arguments but it only used the first to produce the result, and it was called with an overwhelmingly complex calculation in its second argument which is unused, then the strict evaluation of such function's result would waste time and computing resources solving a preliminary expression whose result is not actually useful at all.
+
+On the other hand, strict evaluation has the advantage of being predictable: every operation is done in the same order as it's written. In languages which allow functions to produce side-effects, strict evaluation is needed, because the result may depend on the order of evaluation of operations and functions.
+
+## Side effects and purity
+
+The main reason for using strict evaluation is the presence of side effects, not just because a function uses an external ingredient for producing a result, but that external thing changes over time. The most common scenario is modifying a global variable, but that's not the only one: printing to the screen and reading from a file or a network socket are also valid side effects which may require a certain order.
+
+Since lazy evaluation does not transparently state the order in which things happen, adding side effects on top of it would create plenty of issues! The reason why Haskell does not have side effects (i.e. it's *pure*) is that its designers wanted it to be functional and lazy, and it's not possible if side effects exist.
+
+A language with no side effects at all would not be very useful. Despite that, the Haskell designers found a way to allow the language to "interact with the outside world" (read and write files, print to the screen, etc.) in a principled and restricted way that doesn't interfere with the purity of the language: the IO monad.
+
+## Lazy evaluation
+
+Essentially, lazy evaluation means delaying an evaluation as long as possible, until it's not possible anymore because the result of such evaluation is needed to advance the overall computation.
+
+What happens when an expression is given as argument to a function is that it's packaged up as an *unevaluated expression*, which takes the name of a **thunk**, without doing any actual work. If the expression ends up unused (i.e. the function doesn't use that parameter), it gets cleaned up by the garbage collector.
+
+## Pattern matching drives evaluation
+
+So far we've talked about *whether* an expression is used, but not *when* it is necessary to evaluate it. Let's look at an example.
+
+ f1 :: Maybe a -> [Maybe a]
+ f1 m = [m,m]
+
+ f2 :: Maybe a -> [a]
+ f2 Nothing = []
+ f2 (Just x) = [x]
+
+Both `f1` and `f2` use their arguments, but one needs to evaluate it while the other doesn't. As we can see, `f1` uses its argument `m` but it doesn't need to know anything about it, so it can be put in the list as an unevaluated expression. On the other hand, `f2` needs to know whether its argument was constructed using `Nothing` or `Just`, so it needs to be evaluated because its result depends on it.
+
+Another important aspect is that thunks are only evaluated enough to proceed with computation, and no further. If `f2` was given a very heavyweight computation as argument, wrapped around a `Just`, its evaluation is going to depend on where and how the result of `f2` is used. If it's not needed, it's going to remain uncalculated.
+
+A handy slogan helps us remember the important points: *"pattern matching drives evaluation"*, which means that:
+
+- Expressions are only evaluated when pattern-matched.
+- Expressions are only evaluated as far as needed for the match to proceed.
+
+Although lazy evaluation works like this in theory, in practice GHC uses a more sophisticated technique called *graph reduction*, which represents all expressions as a single graph. In this graph, different parts of the expression can share pointers to the same subexpression, which ensures that no unique expression is evaluated more than once.
+
+## Consequences
+