Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
pp:2023:haskell:l07 [2023/04/19 22:11]
tpruteanu [7.1 Lambda Calculus]
pp:2023:haskell:l07 [2023/04/27 01:17] (current)
mihai.udubasa [Lambda calculus as a programming language (optional)] fix typo
Line 69: Line 69:
 **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**. **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**: \\+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) \\   * **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) \\   * **Applicative Order** evaluation consist of always reducing the //​leftmost//,​ //​innermost//​ **redex** (always reduce the function argument before the function itself) \\
Line 82: Line 82:
   - $ \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.\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 x.y \ (\lambda x.(x \ x) \ \lambda x.(x \ x))$
-==== Booleans ==== 
-We can encode boolean values **TRUE** and **FALSE** in lambda calculus as functions that take 2 values, **x** and **y**, and return the first (for **TRUE**) or second (for **FALSE**) value. \\ 
  
-$ TRUE \lambda x.\lambda y.y$ \\ +==== Lambda calculus as a programming language (optional) ====
-$ FALSE \lambda x.\lambda y.x$ \\+
  
-<​note>​As we defined it, **TRUE** is sometimes called the **K**-Combinator (or //Kestrel//), and **FALSE** the **KI**-Combinator (or //Kite//). </​note>​+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 curious, a series of additional exercises covering this topic can be found here: [[pp:​2023:​haskell:​l07-extra|Lambda Calculus as a programming language]]. \\
  
-<​hidden>​ +===== 7.2 Intro to Haskell =====
-Some common operation on booleans (that were discussed during the lecture) are: \\ +
-\\ +
-$ AND \lambda x.\lambda y.((x \ y) \ x) $ \\ +
-$ OR \lambda x.\lambda y.((x \ x) \ y) $ \\ +
-$ NOT \lambda x.((x \ FALSE) \ TRUE) $ \\+
  
-<​note>​  +**Prequisites**: having ​working haskell environment ​([[pp:​haskell-environment|Haskell Environment]])
-**NOT** can also be written as\\ +
-\\ +
-$ NOT = \lambda x.\lambda ​a.\lambda b.((x \ b) \ a) $ \\ +
-\\ +
-You can convince yourself that this works by evaluating $ NOT \ TRUE $ and $ NOT \ FALSE $. This way of writting **NOT** is also called the **C**-Combinator (or //​Cardinal//​)+
-</​note>​+
  
-</​hidden>​+**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. \\
  
-==== Natural Numbers ====+{{ :​pp:​2023:​haskell:​haskell.png?​nolink&​200 |}}
  
-Church numerals represent natural numbers ​as **higher-order functions**. Under this representationthe number //**n**// is a function that maps **f** to its **n-fold composition**. \\ +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-recursionfolds and maps, but this time in a purely functional context.
-\\ +
-$ N0 = \lambda f.\lambda x. x $ \\ +
-$ N1 = \lambda f.\lambda x. (f \ x) $ \\ +
-$ N2 = \lambda f.\lambda x. (f \ (f \ x)) $ \\ +
-...+
  
-<​note>​ Does **N0** look familiar? It's the same as **FALSE** if you rename the variables (using $\alpha$-reduction)</​note>​+==== A trip through time ==== 
 +Remember: [[pp:​2023:​scala:​l01|Lab 1Introduction to Scala]]
  
-You can also define operation on church numerals, some (that were discussed during ​the lecture) are: \\ +**7.2.1.** Implement a tail-recursive function ​that computes ​the factorial of a natural number. 
-\\ +<code haskell> 
-$ SUCC = \lambda n.\lambda f.\lambda x.(f \ ((n \ f) \ x)) $ \\ +fact :: Int -> Int 
-$ ISZERO ​\lambda n.((n \lambda x.FALSE) \ TRUE) $ \\ +fact undefined 
-$ ADD = \lambda n.\lambda m.\lambda f.\lambda x.((n \ f) ((m \ f) \ x)) $ \\ +</​code>​ 
-\\ +**7.2.2.** Implement a tail-recursive function that computes the greatest common divisor of two natural numbers. 
-**7.1.1** Define multiplication under church numerals$ MULT \lambda n.\lambda m... $ (without using the **Y**-combinator)+<code haskell>​ 
 +mygcd :: Int -> Int -> Int 
 +mygcd a b undefined 
 +</​code>​ 
 +**7.2.3.** Implement the function ''​mySqrt'' ​ which computes the square root of an integer $ a $.
  
-**7.1.2** Define exponentiation under church numerals$ EXP \lambda n.\lambda m... $+==== 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.1.3** (*) Define the predecessor operator, ​that takes number ​and returns ​the number prior to it.+**7.2.4.** Implement funtions ''​mymin''​ and ''​mymax'' ​that take list of ints, and return the smallest/​biggest value in the list.
  
-What'the predecessor ​of 0? Evaluate $ PRED \ N0 $.+**7.2.5.** Implement a function ​''​unique''​ that takes a list of ints, and removes all duplicates. 
 + 
 +**7.2.6.** 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.7.** Extend the function from **7.2.6.** 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>​ 
-$ PRED = \lambda n.\lambda f.\lambda x.(((n \ (\lambda g.\lambda h.h \ (g \ f))) \ (\lambda u.x)) \ (\lambda v.v)) $ \\ 
 \\ \\
-$ \phi = \lambda p.PAIR \ (SND \ p) \ (SUCC \ (SND \ p)) \\ +<​hidden>​ 
-PRED = \lambda.n.FST \ (\ \phi \ (PAIR \ N0 \ N0)) $ \\+You can test **7.2.6.** and **7.2.7.** with the following snippet, if your function is $
 +<code haskell>​ 
 +f [1..n
 +</​code>​
 </​hidden>​ </​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)
  
-**7.1.4** Define substraction under church numerals: $ SUB = \lambda n.\lambda m. \ ... $ (**Hint**: use $ PRED $).+[] -- !!! not a data type, represents the empty list (Nil in Scala) 
 +</​code>​ 
 +</​note>​
  
-What happens if you try to substract a bigger number from a smaller one? Evaluate $ SUB \ N1 \ N2 $.+==== Types in Haskell ====
  
-**7.1.5** Define $ LEQ $ (less or equal)$ LEQ m $ should return **TRUE** if $ n \leq m $ and **FALSE** if $ n m $.+In Haskell, functions are curried by default, ​**i.e.** a function: 
 +<code haskell>​ 
 +f a b = ... 
 +</​code>​ 
 +is the same as: 
 +<code haskell>​ 
 +f = \a -> \b -> ... 
 +</​code>​
  
-**7.1.6** Define ​EQ (equality). ​EQ \ n \ m should return **TRUE** if n = m and **FALSE** otherwise.+So, if is a ''​Int''​ and a ''​Double'',​ and returns a ''​Char'',​ it would have the following type: 
 +<code haskell>​ 
 +f :: Int -> Double -> Char 
 +</​code>​
  
-===== 7.2 Recursion and the Y-Combinator =====+**7.2.8.** Check the type signature of the following functions:​ 
 +  * ''​foldl''​ 
 +  * ''​foldr''​ 
 +  * ''​filter''​ 
 +  * ''​map''​
  
-In lambda calculus, recursion ​is achieved using the fixed-point combinator (or **Y** combinator)\\ +<note important>​ 
-A fixed-point combinator is a **higher-order** function that returns some fixed point of it's argument function (**x** is a fixed pointed for a function **f** if $ f(x) = x $)That means$ f \ (fix \ f= fix \ f $ \\ +If a function ​is not ambigous, ''​ghc''​ can infer the type signature, for **educational** purposes, going forward you will have to write signatures for all functions you define, this is considered good practice and helps prevent bugs
-And by repeated application$ fix \ f = f \ (f \ (... f \ (fix \ f)...)) $ \\ +</​note>​ 
-The **Y**-combinator in lambda calculus looks like this: \\ + 
-\\ +<note tip> 
-$ FIX = \lambda f.(\lambda x.f \ (x \ x)) (\lambda x.f \ (x \ x)) $ +In ''​ghci'',​ you can check the type of a expression with: '':​t''​ 
-\\+</​note>​ 
 + 
 +===== 7.3 Brain Twisters ===== 
 + 
 +**7.3.1.** Implement ''​map''​ using ''​foldl''​ and ''​foldr''​. 
 +<code haskell>​ 
 +mymapl :: (a -> b-> [a] -> [b] 
 +mymapr :: (a -> b-> [a] -> [b] 
 +</​code>​ 
 + 
 +**7.3.2.** Implement ''​filter''​ using ''​foldl''​ and ''​foldr''​. 
 +<code haskell> 
 +myfilterl :: (a -> Bool-> [a] -> [a] 
 +myfilterr :: (a -> Bool-> [a] -> [a] 
 +</​code>​
  
-**7.2.1** Using the **Y**-Combinator, define ​function that computes the factorial of number **n**.+**7.3.3.** Implement ''​foldl''​ using ''​foldr''​. 
 +<code haskell>​ 
 +myfoldl :: (a -> b -a) -> -> [b] -> a 
 +</​code>​
  
-**7.2.2** Using the **Y**-Combinator, define a function $ FIB $ that computes the **n**-th fibonacci number.+**7.3.4.** Implement ''​bubbleSort''​. 
 +<code haskell>​ 
 +bubbleSort :: [Int] -> [Int] 
 +</​code>​
  
-===== 7.3 Intro to Haskell =====+**7.3.5.** Implement ''​quickSort''​. 
 +<code haskell>​ 
 +quickSort :: [Int] -> [Int] 
 +</​code>​