Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:2023:haskell:l07 [2023/04/19 22:45] tpruteanu |
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 86: | Line 86: | ||
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). \\ | 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]]. \\ | + | 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]]. \\ |
===== 7.2 Intro to Haskell ===== | ===== 7.2 Intro to Haskell ===== | ||
+ | |||
+ | **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. \\ | ||
+ | |||
+ | {{ :pp:2023:haskell:haskell.png?nolink&200 |}} | ||
+ | |||
+ | 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. | ||
+ | <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> | ||
+ | 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 $. | ||
+ | |||
+ | ==== 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 ''mymin'' and ''mymax'' that take a list of ints, and return the smallest/biggest value in the list. | ||
+ | |||
+ | **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> | ||
+ | You can test **7.2.6.** and **7.2.7.** 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> | ||
+ | |||
+ | ==== Types in Haskell ==== | ||
+ | |||
+ | 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> | ||
+ | |||
+ | So, if $ a $ is a ''Int'' and $ b $ a ''Double'', and $ f $ returns a ''Char'', it would have the following type: | ||
+ | <code haskell> | ||
+ | f :: Int -> Double -> Char | ||
+ | </code> | ||
+ | |||
+ | **7.2.8.** Check the type signature of the following functions: | ||
+ | * ''foldl'' | ||
+ | * ''foldr'' | ||
+ | * ''filter'' | ||
+ | * ''map'' | ||
+ | |||
+ | <note important> | ||
+ | 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. | ||
+ | </note> | ||
+ | |||
+ | <note tip> | ||
+ | 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.3.3.** Implement ''foldl'' using ''foldr''. | ||
+ | <code haskell> | ||
+ | myfoldl :: (a -> b -> a) -> a -> [b] -> a | ||
+ | </code> | ||
+ | |||
+ | **7.3.4.** Implement ''bubbleSort''. | ||
+ | <code haskell> | ||
+ | bubbleSort :: [Int] -> [Int] | ||
+ | </code> | ||
+ | |||
+ | **7.3.5.** Implement ''quickSort''. | ||
+ | <code haskell> | ||
+ | quickSort :: [Int] -> [Int] | ||
+ | </code> |