====== 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: **a.)** foldr ((||).(==1)) False [2,1,3,4,5,6,7] **b.)** 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. Infinite Binary Trees ===== Defining a simple **binary tree** structure in //Haskell// is easy.\\ Take this for example: data BTree = Node Int BTree BTree | Nil deriving Show {- 1 | ---------- | | 2 3 ------ ------ | | | | Nil Nil Nil Nil -} tree :: BTree tree = Node 1 (Node 2 Nil Nil) (Node 3 Nil Nil) But what if we want to enforce an **infinite binary tree**?\\ The solution is eliminating the need for the empty node ''Nil'': -- This is an infinite data type, no way to stop generating the tree data StreamBTree = StreamNode Int StreamBTree StreamBTree **8.3.1.** Define the ''repeatTree'' function which takes an **Int** ''k'' and generates an **infinite tree**, where each node has the value ''k''. {- > repeatTree 3 3 | --------- | | 3 3 ------ ------ | | | | 3 3 3 3 . . . . . . . . -} repeatTree :: Int -> StreamBTree **8.3.2.** In order to view an **infinite tree**, we need to convert it to a **finite binary tree**. Define the function ''sliceTree'', which takes a level ''k'', an **infinite tree** and returns the first ''k'' levels of our tree, in the form of a **finite** one. {- > sliceTree 2 (repeatTree 3) 3 | ---------- | | 3 3 ------ ------ | | | | Nil Nil Nil Nil -} sliceTree :: Int -> StreamBTree -> BTree **8.3.3.** Define the ''generateTree'' function, which takes a **root** ''k'', a **left generator** function ''leftF'' and a **right generator** function ''rightF''.\\ For example, let's say we have ''k=2'', ''leftF=(+1)'', ''rightF=(*2)''. This should generate a tree where the //root// is $math[2], the //left child// is $math[parent + 1]\\ and the //right child// is $math[parent * 2]. {- > generateTree 2 (+1) (*2) 2 | ------------------------------------------- | | 3 4 ----------------- ----------------- | | | | 4 6 5 8 --------- --------- --------- --------- | | | | | | | | 5 8 7 12 6 10 9 16 ----- ----- ----- ----- ----- ----- ----- ----- | | | | | | | | | | | | | | | | 6 10 9 16 8 14 13 24 7 12 11 20 10 18 17 32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -} generateTree :: Int -> (Int -> Int) -> (Int -> Int) -> StreamBTree ===== 8.4. Numerical Approximations ===== **8.4.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)), .%%.%%. ]''. {- with this function, you should be able to easily define the natural numbers with something like: build (+1) 0 -} build :: (Double -> Double) -> Double -> [Double] **8.4.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.4.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: $math[\lvert s_n - s_{n+1} \rvert < e]. select :: Double -> [Double] -> Double ==== Mathematical Constants ==== **8.4.4.** Knowing that $math[\displaystyle \lim_{n \rightarrow \infty} \frac{F_{n+1}}{F_n} = \varphi], where $math[F_n] is the nth element of the **Fibonacci** sequence, write an\\ approximation with **tolerance** ''e=0.00001'' of the **Golden Ration** ($math[\varphi]). Use the previously defined ''fibs'' **stream** and the ''select'' function. phiApprox :: Double **8.4.5.** Consider the //sequence//: $math[a_{n+1} = a_n + sin(a_n)]; where $math[a_0] is an //initial approximation//, randomly chosen (but **not** 0 because $math[a_{n+1} != a_n]). Knowing that $math[\displaystyle \lim_{n \rightarrow \infty} a_n = \pi], write an approximation with **tolerance** ''e=0.00001'' of **$math[\pi]**. Make sure to use ''build'' and ''select''. piApprox :: Double ==== Square Root ==== **8.4.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//: $math[a_{n+1} = \frac{1}{2}(a_n + \frac{k}{a_n})]; where $math[a_0] is an //initial approximation//, randomly chosen. Knowing that $math[\displaystyle \lim_{n \rightarrow \infty} a_n = \sqrt{k}], write a function that approximates $math[\sqrt{k}] with **tolerance** ''e=0.00001''. Use ''build'' and ''select''. sqrtApprox :: Double -> Double ==== Derivatives ==== **8.4.7.** We can approximate the derivative of a function in a certain point using the definition of the derivative: $math[\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 $math[a], using a smaller $math[h]. **a)** generate the sequence: $math[h_0, \frac{h_0}{2}, \frac{h_0}{4}, \frac{h_0}{8}, ...] (where $math[h_0] is a randomly chosen //initial approximation//)\\ **b)** generate the list of approximations for $math[f'(a)], using the formula above\\ **c)** write the function that takes a function $math[f] and a point $math[a] and approximates $math[f'(a)] with tolerance ''e=0.00001'', using the previous steps. derivativeApprox :: (Double -> Double) -> Double -> Double ===== Recommended Reading ===== * [[http://worrydream.com/refs/Hughes-WhyFunctionalProgrammingMatters.pdf| Why Functional Programming Matters (especially section 4 "Gluing Programs Together", where the lab exercises are inspired from)]]