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. 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]
8.2. Streams
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. Numerical Approximations
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