Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Lab 7. Lambda Calculus. Intro to Haskell ====== ===== 7.1 Lambda Calculus ===== Lambda Calculus represent a axiomatic system that can be used for very basic proofs. \\ Given a set of variables **VARS**, a expression under lambda calculus can be: \\ | // variable // | $ x $ | $ x \in VARS $ | | // function // | $ \lambda x.e $ | $ x \in VARS $, $ e $ is a $ \lambda $-expression | | // application // | $ (e_1 \ e_2) $ | $ e_1, e_2 $ are $ \lambda $-expressions | To evaluate $\lambda$-expressions, there are two types of reduction operations: * **$\alpha$-conversion**: given a expression: $ \lambda x.e $, you can rename all occurences of //**x**// in //**e**// with //**y**// (used for avoiding **name collisions**). * **$\beta$-reduction**: given a expression: $ \lambda x.body \ param$, you can replace all occurences of //**x**// in //**body**// with //**param**// (We will denote this action with: $ body[x \ / \ param] $). <note warning> Be careful about the difference between $ E_1 = \lambda x.e_1 \ e_2 $ **and** $ E_2 = e_1[x \ / \ e_2] $. The former denotes a expression made from a // application // between a // function // and a // expression //, while the latter is the // expression // obtained applying $ \beta $**-reduction** to the former. We say $ E_1 $ is reducible to $ E_2 $ ($ E_1 => E_2 $). </note> <note important> We say two // expressions // are equal, if it is possible to get one of them from the other using only **$\alpha$-conversion**. If a // expression // cannot be reduced further using $ \beta $**-reductions**, we say the expression is in $ \beta $**-normal form**. </note> ==== Free and bound variables ==== Take the following Scala snippet as an example: <code scala> def f(x: Int) = x + y </code> We can say that the second occurence of $ x $ is // bounded // by the $ x $ that appears as a function parameter. When we call the function, the occurence of $ x $ is replaced by the argument that was provided to $ f $. In contrast, $ y $ is a // free // variable. \\ This code might look weird, where does $ y $ come from? What does it do? Why would we use a variable that we don't instantiate (i.e. is not bound to anything)? Well, the snippet actually comes from a broader context: <code scala> def g(x: Int, y: Int) = { def f(x: Int) = x + y f(x * y) } </code> In this new snippet we can see that all variables are //bounded//, and the free variable from before is // bounded // by the outer function, but only the // free // variable, notice that $ x $ is still bounded by the inner function, and the $ x $ parameter of $ g $ is ignored inside $ f $. \\ The importance of // free // variables is that only // free occurences // of a sub-expression can be bounded by the outer expression. \\ \\ Translating to lambda calculus, when reducing $ \lambda x.e_1 \ e_2 $ to $ e_1[x \ / \ e_2] $, only // free // occurences of $ x $ in $ e_1 $ will be replaced by $ e_2 $. More generally, we say that: * if all occurences of a variable in a expressions are // bounded //, the variable is said to be // bounded // * if one occurence of a variable in a expressions is // free //, the variable is said to be // free // \\ **Exercise** \\ **7.1.1. ** For every variable occurence, mention if it's a // free // or a // bounded // occurence: - $ \lambda y.(\lambda x.x \ (x \ y)) $ - $ \lambda x.(x \ \lambda y.(x \ y \ z)) \ (x \ \lambda y.x) $ - $ \lambda f.((\lambda x.(f \ (x \ x))) \ (\lambda x.(f \ (x \ x)))) $ ==== Reduction rules ==== Using what we learned from // free // and // bounded // variables, we can define a algorithm for $\beta$**-reduction**, given a expression $ e_1[x \ / \ e_2] $: ^ $ e_1 $ ^ $ e_1[x \ / \ e_2] $ ^ // condition // ^ | $ x $ | $ e_2 $ | | | $ y $ | $ y $ | $ x \neq y $ | | $ E_1 \ E_2 $ | $ E_1[x \ / \ e_2] \ E_2[x \ / \ e_2] $ | | | $ \lambda x.e $ | $ \lambda x.e $ | | | $ \lambda y.e $ | $ \lambda y.e[x \ / \ e_2] $ | $ x \neq y $, $ y $ does not appear // free // in $ e_2 $| | $ \lambda y.e $ | $ \{\lambda z.e[y \ / \ z]\}[x \ / \ e_2] $ | $ x \neq y $, appears // free // in $ e_2 $| ( $ z $ is a new variable that is not free in $ e $ or $ e_2 $ ) | ==== Evaluation order ==== **Q:** If we have multiple **redexes** in a expression, which one do we evaluate? **A:** We can evaluate any of them, and it is guaranteed by [[https://en.wikipedia.org/wiki/Church%E2%80%93Rosser_theorem | Church-Rosser theorem]] that if the expression is reducible, we will eventually get the same $ \beta $**-normal form**. To not just randomly choose **redexes**, there exist // reduction strategies //, from which we will use the **Normal Order** and **Applicative Order**: \\ * **Normal Order** evaluation consist of always reducing the //leftmost//, //outermost// **redex** (whenever possible, subsitute the arguments into the function body) \\ * **Applicative Order** evaluation consist of always reducing the //leftmost//, //innermost// **redex** (always reduce the function argument before the function itself) \\ <note important> A expression of the form $ \lambda x.e_1 \ e_2 $ is also called a **redex** (reducible expression) </note> **Exercise** \\ **7.1.2. ** Evaluate in both **Normal Order** and **Applicative Order** the following expressions: - $ \lambda x.\lambda y.(x \ y \ x) \ \lambda x.\lambda y.x \ (\lambda x.\lambda y.\lambda z.(x \ z \ y) \ \lambda x.\lambda y.y)$ - $ \lambda x.y \ (\lambda x.(x \ x) \ \lambda x.(x \ x))$ ==== Lambda calculus as a programming language (optional) ==== The [[https://en.wikipedia.org/wiki/Church%E2%80%93Turing_thesis | Church-Turing thesis]] asserts that any //computable// function can be computed using lambda calculus (or Turing Machines or equivalent models). \\ For the curios, a series of additional exercises covering this topic can be found here: [[pp:2023:haskell:l07-extra|Lambda Calculus as a programming language]]. \\ ===== 7.2 Intro to Haskell ===== {{ :pp:2023:haskell:haskell.png?nolink&200 |}} **Prequisites**: having a working haskell environment ([[pp:haskell-environment|Haskell Environment]]) **Haskell** is a general-purpose, purely functional programming language, that we will use for the rest of the semester to showcase functional patterns and programming styles. \\ This section is designed for us to get comfortable with haskell syntax, we will use several concept that we learned in Scala, such as tail-recursion, folds and maps, but this time in a purely functional context. ==== A trip through time ==== Remember: [[pp:2023:scala:l01|Lab 1. Introduction to Scala]] **7.2.1.** Implement a tail-recursive function that computes the factorial of a natural number. Start from the code stub below: <code haskell> fact :: Int -> Int fact = undefined </code> **7.2.2.** Implement a tail-recursive function that computes the greatest common divisor of two natural numbers: <code haskell> gcd :: Int -> Int -> Int gcd a b = undefined </code> **7.2.3.** Implement the function ''mySqrt'' which computes the square root of an integer $ a $. ==== Lists ==== The following Scala syntax for working with lists, can be translated to Haskell as follows: ^ Scala ^ Haskell cases ^ Haskell pattern matching ^ Haskell guards ^ |<code scala> def f(l: List[Int]) = l match { case Nil => ... case (x::xs) => ... } </code>|<code haskell> f l = case l of { [] -> ... (x:xs) => ... } </code> | <code haskell> f [] = ... f (x:xs) = ... </code> | <code haskell> f l | l == [] = ... | otherwise = ... </code> | **7.2.4.** Implement funtions ''minimum'' and ''maximum'' that take a list of ints, and return the smallest/biggest value in the list. **7.2.5.** Given a list of ints, return a list of strings where for each element, return: * **'Fizz'** if the number is divisible by 3 * **'Buzz'** if the number is divisible by 5 * **'FizzBuzz'** if the number is divisible by 3 **and** 5 * a string representation of the number otherwise **7.2.6.** Extend the function from **7.2.5.** with the following rules: * **'Bazz'** if the number is divisible by 7 * **'FizzBazz'** if the number is divisible by 21 * **'BuzzBazz'** if the number is divisible by 35 * **'FizzBuzzBazz'** if the number is divisible by 105 \\ <hidden> You can test **7.2.5.** and **7.2.6.** with the following snippet, if your function is $ f $: <code haskell> f [1..n] </code> </hidden> <note> In Haskell, the list data type is denote by the type the list holds surrounded by square paranthesis. <code haskell> [Int] -- list of ints [Double] -- list of doubles [[Int]] -- list of lists of ints (matrices) [] -- !!! not a data type, represents the empty list (Nil in Scala) </code> </note>