====== 5. Algebraic Datatypes in Haskell ====== There are multiple ways of defining new data types in Haskell. In this session, we will focus on //Algebraic Data Types//, which allow us to specify multiply constructors, with any number of arguments. A new ADT can be defined using the keyword ''data'' followed by the desired name (necessarily capitalized!), an equal sign and then all constructors (capitalized!) separated by a pipe ''|'': data Ordering = LT | EQ | GT This type is already defined in haskell and is used whenever comparisons are needed (its constructors stand for "less than", "equal" and "greater than"). Think of how the standard C function ''strcmp'' takes two strings and returns a value indicating their lexicographic order: * a negative number if the first string comes first * 0 if they are equal * a positive number if the second string comes first In Haskell, it would return an ''Ordering''. 1. Write a function ''myCompare'' which takes two integers and returns an ''Ordering'' indicating their relationship (there is already a function in Haskell, ''compare'', which does this for all types of numbers). 2. Write a function which takes an integer and a **sorted** (ascending) list of integers and inserts that number into the proper position so that the resulting list is still sorted. insert 7 [1,5,22,86] -> [1,5,7,22,86] 3. Write a function ''insertSort'' that makes use of ''insert'' to implement [[https://en.wikipedia.org/wiki/Insertion_sort|insertion sort]] on a list. We now have a sorting function. However, it is unsatisfactory, because it can only sort numbers in an ascending order. We cannot specify any other criteria (e.g. descending order, by number of prime factors, by number of digits). We will expand the functionality by employing the ''Ordering'' data type and an auxiliary "comparator function" which takes two integers and returns an ''Ordering''. 4. Write a function ''insertBy'' which takes a comparator function, an integer and a sorted list of integers (s.t. $ \forall i, j; i < j, comparator(l_i, l_j) \neq GT$) and uses the comparator to insert the element at the proper location. 5. Write a function ''insertSortBy'' which insert-sorts a list, with the aid of a comparator function (there is a function in the ''Data.List'' module named ''sortBy'' that does the same thing, though it uses another sorting algorithm). A data type's constructors can also take arguments; when defining the data type, we need to specify the types of these arguments: data Point = Point Float Float Note that the constructor here can have the same name as the data type, because they are very different things and could never cause confusion (they can never appear in the same contexts). Once defined this way, we can use regular **pattern matching** for our constructors: pointToPair :: Point -> (Float, Float) pointToPair (Point x y) = (x, y) 6. Write a function ''distance'' which takes two points and computes their euclidian distance. 7. Write a function ''collinear'' which takes three points and tells whether they are on the same line. (one way to do this is to compute the area of the triangle they define and make sure it's 0). If you ask ''ghci'' to print a ''Point'' (e.g. by simply typing ''Point 2.0 3.1'' at the prompt) it will throw an error, as it doesn't know how to do that. You can use the following definition to obtain a "default" representation. data Point = Point Float Float deriving Show The meaning of ''deriving Show'' will be discussed in a future session. We can also define **recursive data types**, whose constructors can depend on another value of the same type. data Natural = Zero | Succ Natural Here, we say that a natural number is either the number zero, or the successor of another natural number (see [[https://en.wikipedia.org/wiki/Peano_axioms|Peano axioms]]). The number three would be ''Succ (Succ (Succ Zero)))''. 8. Write a function ''add'' which takes two ''Natural'' numbers and returns their sum (remember that you can do pattern matching to check the structure of a number). 9. Write a function ''mul'' which takes two ''Natural'' numbers and returns their product. Again, it might be useful to display these data types so add ''deriving Show'' to the definition: data Natural = Zero | Succ Natural deriving Show Using recursive data types, we can also define immutable lists of integers: data IList = IVoid | ICons Int IList However, we have seen that haskell lists are generic, capable of containing any type of element, not just integers. For this we need a **polymorphic data type** (as opposed to all the previous ones which are **monomorphic**). That is, a type that depends on another type. data List a = Void | Cons a (List a) In the above definition, we have mostly replaced ''Int'' with ''a''. ''a'' is a **type variable**; it can stand for any type (what makes it a type variable is that it starts with a lowercase letter; it could as well be ''b'' or ''something''). We can now construct lists of any type. However, remember that all elements in a list must be of the //same// type; we can't combine integers and strings. That is why ''a'' also follows the data type name, on the left hand side of the ''='': if we have a ''List'' which contains ''Int''s, then that is a ''List Int''. 10. Write a function ''myLength'' which takes a ''List a'' and returns its length. Notice how the type of the lists elements doesn't matter (and remember pattern matching!). 11. Write a function ''toHaskell'' which converts our lists to haskell's lists. That is, it takes a ''List a'' and returns a ''[a]''. 12. Define a **recursive**, **polymorphic** data type to represent binary trees. A binary tree is either the empty tree, or a node containing a value, a left subtree and a right subtree. 13. Write a function ''height'' which takes a binary tree and returns its height. 14. Write a function ''size'' which takes a binary tree and returns the number of nodes. 15. Write a function ''mirror'' which takes a tree and mirrors it (for each node, the left subtree becomes the right subtree and vice-versa). 16. Write a function ''treeMap'' which takes a function ''a -> b'' and a tree with values of type ''a'' and applies the function on each of the tree's elements to yield a tree with values of type ''b'' (exactly like ''map'' for lists). 17. Write a function ''flatten'' which takes a binary tree and collects all its values into a list (note that there are multiple ways to go through all the values, such as [[https://en.wikipedia.org/wiki/Tree_traversal|preorder, inorder, postorder]]; the choice is up to you). Consider the following data type which defines a ''Student'', charactarized by a first name, a last name and a list of grades: data Student = Student String String [Float] 18. Write a function ''avg'' to compute the average of a student's grades. If you use ''length'' on a list and want to divide to it, you'll notice haskell raises an error when asked to divide a ''Float'' to an ''Int''; you can call ''fromIntegral'' on an ''Int'' to perform an explicit conversion. 19. Write a function ''studComp'' which takes two students and returns an ''Ordering'' based on their averages. 20. Write a function ''highestAverage'' which takes a list of students and returns the name and surname (separated by a space) of the student with the highest average. The following data type models simple arithmetic expressions, where we can have variables, integer constants and addition and multiplication: data AExpr = Const Int | Var String | Add AExpr AExpr | Mul AExpr AExpr Similarly, the following models simple boolean expressions that are either an equality/greather-than comparison between two arithmetic expressions or a negation of a boolean expressions: data BExpr = Eq AExpr AExpr | Not BExpr | Gt AExpr AExpr Our goal is to be able to evaluate an expression thus represented. First, we need a way to deal with variables: for this we define a context, which is a collection of mappings from a variable name to a value: type Context = [(String, Int)] Note that we used the more lightweight ''type'' to obtain a **type synonym**, because we don't actually need the power of ADTs. 21. Write a function ''search'' which takes a ''Context'' and a variable name and returns the value from that context (it is guaranteed, that exactly one value exists for each variable on which the function is called; you don't need to account for any edge-case). 22. Write a function ''evalA'' which takes a ''Context'' and an ''AExpr'' and returns a single integer representing its evaluated value. 23. Write a function ''evalB'' which takes a ''Context'' and a ''BExpr'' and returns a single boolean representing its evaluated value.