This is an old revision of the document!


8. Lazy Evaluation

When passing parameters to a function, programming language design offers two options which are not mutually exclusive (both strategies can be implemented in the language):

  • applicative (also called strict) evaluation strategy:
    • parameters are always evaluated first
    • can be further refined into: call-by-value (e.g. as it happens in C) and call-by-reference (e.g. as it happens for objects in Java).
  • normal evaluation strategy (also called non-strict, and when implemented as part of the PL - call-by-name)
    • the function is always evaluated first
    • can be further refined into: lazy, which ensures that each expression is evaluated at most once

For more details, see the lecture on lazy evaluation. In Haskell, the default evaluation strategy is lazy.
However, there are ways in which we can force evaluation to be strict. In this lab, we will explore several programming constructs which benefit from lazy evaluation.

8.1.1. Describe the evaluation strategy of the following expressions:

foldr ((||).(==1)) False [2,1,3,4,5,6,7]
foldl (flip ((||).(==1))) False [2,1,3,4,5,6,7]

For this lab, streams are synonymous with infinite lists.
If you want to see their content, use the take function (:t take).

8.2.1. Define the stream of natural numbers.

nats :: [Integer]

8.2.2. Using the stream of natural numbers, define the stream of odd numbers and perfect squares. Hint: use higher order functions for a quick solution.

-- 1, 3, 5, 7, ...
odds :: [Integer]
-- 0, 1, 4, 9, ...
squares :: [Integer]

8.2.3. Define the stream of Fibonacci numbers. Hint: :t zipWith.

fibs :: [Integer]

8.3.1. Define the build function which takes a generator g and an initial value a0 and generates the stream: [a0, g a0, g (g a0), g (g (g a0)), ... ].

{-
    having this function, you should
    easily be able to define the natural numbers
    with something like: build (+1) 0
-}
 
build :: (Double -> Double) -> Double -> [Double]

8.3.2. Using the build function, define the following streams:

-- 0, 1, 0, 1, 0, 1, 0, ...
alternatingBinary :: [Double]
 
-- 0, -1, 2, -3, 4, -5, ...
alternatingCons :: [Double]
 
-- -1, 2, -4, 8, -16, ...
alternatingPowers :: [Double]

8.3.3. Define the select function which takes a tolerance e and a stream s and returns the nth element of the stream which satisfies the following condition: $ \lvert s_n - s_{n+1} \rvert < e$ .

select :: Double -> [Double] -> Double

Mathematical Constants

8.3.4.

Knowing that $ \displaystyle \lim_{n \rightarrow \infty} \frac{F_{n+1}}{F_n} = \varphi$ , where $ F_n$ is the nth element of the Fibonacci sequence, write an
approximation with tolerance e=0.00001 of the Golden Ration ($ \varphi$ ). Use the previously defined fibs stream and the select function.

phiApprox :: Double

8.3.5.

Consider the sequence:

$ a_{n+1} = a_n + sin(a_n)$ ; where $ a_0$ is an initial approximation, randomly chosen (but not 0 because $ a_{n+1} != a_n$ ).

Knowing that $ \displaystyle \lim_{n \rightarrow \infty} a_n = \pi$ , write an approximation with tolerance e=0.00001 of $ \pi$ . Make sure to use build and select.

piApprox :: Double

Square Root

8.3.6.

Given a number k, we want to create a function which calculates the
square root of k. This is another place where laziness and streams come into play.

Consider the following sequence:

$ a_{n+1} = \frac{1}{2}(a_n + \frac{k}{a_n})$ ; where $ a_0$ is an initial approximation, randomly chosen.

Knowing that $ \displaystyle \lim_{n \rightarrow \infty} a_n = \sqrt{k}$ , write a function that approximates $ \sqrt{k}$ with tolerance e=0.00001. Use build and select.

sqrtApprox :: Double -> Double

Derivatives

8.3.7.

We can approximate the derivative of a function in a certain point using the definition of the derivative:

$ \displaystyle f'(a)=\lim_{h \rightarrow 0} \frac{f(a+h)-f(a)}{h}$

We can obtain better successive approximations of the derivative in a point $ a$ , using a smaller $ h$ .

a) generate the sequence: $ h_0, \frac{h_0}{2}, \frac{h_0}{4}, \frac{h_0}{8}, \ldots$ (where $ h_0$ is a randomly chosen initial approximation)
b) generate the list of approximations for $ f'(a)$ , using the formula above
c) write the function that takes a function $ f$ and a point $ a$ and approximates $ f'(a)$ with tolerance e=0.00001, using the previous steps.

derivativeApprox :: (Double -> Double) -> Double -> Double

The Newton-Raphson Method

8.3.8.

The method used for approximating the square root is derived from a more general one, named The Newton-Raphson Method.

This is a generic method used for finding the roots of a function ($ x$ | $ f(x) = 0$ ).

Considering the following sequence:

$ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$

and the limit:

$ \displaystyle \lim_{n \rightarrow \infty} x_n = r\ \Rightarrow \ f(r) = 0$

write a function which takes another function and it approximates a root with tolerance e=0.00001. Make use of build, select and derivativeApprox.

rootApprox :: (Double -> Double) -> Double

Integrals (:<

8.3.9.

Given a function $ f$ , we can approximate the definite integral on $ [a, b]$, using the area of the trapezoid defined by $ a, b, f(a), f(b)$ :

$ \displaystyle \int_{a}^{b} f(x) dx \approx (b - a)\frac{f(a)+f(b)}{2}$

We can obtain a better approximation by dividing the interval in two and adding the area of the two trapezoids defined by $ a, m, f(a), f(m)$
and $ m, b, f(m), f(b)$ (where $ m$ is the middle of the interval $ [a, b]$). We can obtain a better approximation by dividing these intervals in two and so on.

Write a function which approximates the integral of a function on an interval. Follow the steps:

a) Write a function which takes a function $ f$ and two points $ a, b$ and calculates the area of the trapezoid $ a, b, f(a), f(b)$

trapezoidArea :: (Double -> Double) -> Double -> Double -> Double

b) Write a function which takes a (ascending) list of points and inserts between any two points their middle:

-- [1, 4, 7, 10, 13] ->[1, 2.5, 4, 5.5, 7, 8.5, 10, 11.5, 13]
insertMiddle :: [Double] -> [Double]

c) Write a function which takes a function $ f$ and a list of points $ p_0,\ p_1,\ p_2,\ p_3,\ \ldots$ and returns the list containing the areas of trapezoids defined by two consecutive points:

-- f -> [p0, p1, p2, p3, ...] -> [area p0 p1 f(p0) f(p1), area p1 p2 f(p1) f(p2), area p2 p3 f(p2) f(p3)]
areaConsecutive :: (Double -> Double) -> [Double] -> [Double]

d) Write a function which takes a function $ f$ and two points $ a, b$ and approximates $ \displaystyle \int_{a}^{b} f(x) dx$ with tolerance e=0.00001, using the previous steps.

integral :: (Double -> Double) -> Double -> Double -> Double