Lazy evaluation means that:
We illustrate point 1. via the following example:
nats = 0:(map (+1) nats) test = foldr (\x y-> if x > 2 then 0 else x+y) 10 nats
nats
is a recursive non-terminating expression, which will produce the list of natural numbers, until memory is depleted. To examine this, it is sufficient to call nats
in the interpretertest
is an expression which evaluates to 3
, although it relies on nats
for the computation:op = \x y→ if x > 2 then 0 else x+y
. Then, in effect, test
attempts to compute the expression:0 op (1 op (2 op (3 op (4 op ....
(3 op (4 op ….
the value of the second operand is not used (since x>3
), hence (4 op ….
is not evaluated. The result is 0. Thus, test
actually computes0 op (1 op (2 op 0))
10
) is not actually used.To illustrate point 2. consider:
evens = zipWith (+) nats nats some = take 2 evens
We also recall that:
take 0 _ = [] take n (h:t) = h:(take (n-1) t) zipWith op (x:xs) (y:ys) = (op x y):(zipWith xs ys) zipWith _ _ _ = []
No expression is evaluated until we call some
, in the interpreter. Thus, we start with the following un-evaluated expressions:
Variable | Expression | Value |
---|---|---|
evens | ? | unevaluated |
some | ? | unevaluated |
nats | ? | unevaluated |
Upon calling some
we obtain the following result which requires us to evaluate evens
. This happens due to the pattern-matching definition of take
, which requires a value of the form (x:xs)
.
evens | ? | unevaluated |
some | take 2 evens | unevaluated |
nats | ? | unevaluated |
To evaluate evens
, as before, the pattern-matching definition of zipWith
requires the first element of nats
:
evens | zipWith (+) nats nats | unevaluated |
some | take 2 evens | unevaluated |
nats | ? | unevaluated |
Note that we only require x
and y
to evaluate zipWith in one step, hence (map (+1) nats)
is not (yet) evaluated.
evens | zipWith (+) nats nats | unevaluated |
some | take 2 evens | unevaluated |
nats | 0:(map (+1) nats) | unevaluated |
Thus, the evaluation of zipWith
yields:
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | take 2 evens | unevaluated |
nats | 0:(map (+1) nats) | unevaluated |
Although we have created additional column in the table, we stress that the expressions (map (+1) nats)
which appear in the body of nats
, xs
and ys
are actually the same, and not different identical expressions. The first-step evaluation of take 2 evens
is now complete:
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | 0:(take 1 t) | unevaluated |
nats | 0:(map (+1) nats) | unevaluated |
xs,ys | (map (+1) nats) | unevaluated |
t | zipWith (+) xs ys | unevaluated |
As before, note that both occurrences of zipWith (+) xs ys
are actually the same expression. We continue with another step in the evaluation of take
, which leads to evaluating zipWith (+) xs ys
, and subsequently, xs
and ys
:
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | 0:(take 1 t) | unevaluated |
nats | 0:1:(map (+1) nats) | unevaluated |
xs,ys | 1:(map (+1) nats) | unevaluated |
t | zipWith (+) xs ys | unevaluated |
Note that after evaluating the expression (map (+1) nats)
in one step, the expression is not re-evaluated, as shown in the table above.
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | 0:(take 1 t) | unevaluated |
nats | 0:1:(map (+1) nats) | unevaluated |
xs,ys | 1:(map (+1) nats) | unevaluated |
t | (1+1):zipWith (+) xs' ys ' | unevaluated |
We have now finished the second step in the evaluation of take
. We omit adding variables xs', ys
' and t
'.
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | 0:2:(take 0 t') | unevaluated |
nats | 0:1:(map (+1) nats) | unevaluated |
xs,ys | 1:(map (+1) nats) | unevaluated |
t | (1+1):zipWith (+) xs' ys ' | unevaluated |
Now, take 0 t
' evaluates to []
, hence we finally get:
evens | (0+0):zipWith (+) xs ys | unevaluated |
some | 0:2:[] | evaluated |
nats | 0:1:(map (+1) nats) | unevaluated |
xs,ys | 1:(map (+1) nats) | unevaluated |
t | (1+1):zipWith (+) xs' ys ' | unevaluated |
and the evaluation stops.
Consider two strings s1
and s2
. We define the edit distance between s1
and s2
as the minimal number of edit operations which make the strings identical. The allowed edit operations are:
text
and ext
have edit distance 1)tet
and text
have edit distance 1)text
and tent
have edit distance 1)
As an example, the strings maple
and apple
have edit distance 2:
maple
and obtain aple
p
at the second position in aple
and obtain apple
Dynamic programming computes the edit distance between two strings by building a matrix d
having size(s1)+1
lines and size(s2)+1
columns, where d[i][j]
represents the edit distance between the substrings s1[0:i]
and s2[0:j]
:
d[i][0] = i
for all lines (the distance between the empty string and the (sub)-string s1[0:i]
is i
)d[0][i] = i
for all columns (the distance between the empty string and the (sub)-string s2[0:i]
is i
)s1[i]
and s2[i]
coincide, then d[i][j] = d[i-1][j-1]
d[i][j]
is computed by applying the edit operation which minimises distance. Concretely, d[i][j]
is the minimal of d[i-1][j] + 1
(delete character i
from s1
), d[i-1][j-1] + 1
(modify character i
from s1
), d[i][j-1] + 1
(insert character i
in s1
)Dynamic programming (for edit distance) can be efficiently implemented in Haskell, by exploiting lazyness in order to compute only those necessary distances, and only once. We first start with an example of an implementation of the infinite list of Fibonacci numbers:
fibo = 0:1:(zipWith (+) fibo (tail fibo))