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 9. Algebraic Data Types ====== Types are an essential component in haskell. They start with capital letters, like ''Int''. We have multiple ways of customizing data types. ===== Type Aliases ===== The ''type'' keyword is used to introduce **type aliases** (new names for **already existing** types). Its main role is to improve the clarity of type annotations by renaming complex types to simpler ones or providing additional context. <code haskell> type Matrix = [[Int]] type Name = String type PhoneNumber = String PhoneBook = Map Name PhoneNumber </code> ===== Creating Data Types ===== We can also create **new** types using the ''data'' keyword. Each type has one or more constructors (separated by ''|'' in the type's definition). Each constructor can take parameters, and the type itself may take type parameters (enabling the creation of generic types). <code haskell> data BinaryDigit = Zero | One data Maybe a = Just a | Nothing data List a = Cons a (List a) | Void data Both a b = Both a b </code> To create objects of the given types, we use the constructors as if they were functions: <code haskell> bDigitOne :: BinaryDigit bDigitOne = One maybeFive :: Maybe Int maybeFive = Just 5 myIntList :: List Int myIntList = Cons 1 (Cons 2 Void) </code> Pattern matching can be used on any types defined with ''data''. <code haskell> listHead :: List a -> a listHead (Cons x _) = x listHead Void = undefined </code> ===== Records ===== Additionally, we can use ''records'' to give names to specific paraneters of constructors: <code haskell> data Dog = Dog{ name :: String , breed :: String , age :: String } </code> Using this syntax allows us to create objects like this: <code haskell> myDog = Dog{name="Spike", breed="Golden Retriever", age = 5} </code> Additionally, it defines a 'getter' function for each named field: <code haskell> name myDog -- "Spike" breed myDog -- "Golden Retriever" age myDog -- 5 </code> <note> The record syntax can be used for types with multiple constructors too. In this case, constructors can also share parameter names as long as the types of parameters with the same name match. Example: <code haskell> data Expr = Atom Int | Add {left:: Expr, right:: Expr} | Subtract {left:: Expr, right: Expr} | ... -- here, 'left' from 'Add' needs to have the same type as 'left' from 'Subtract' </code> </note> ===== Newtype ===== Another, more specialised, way of creating new types is the ''newtype'' keyword. It is used to create single-constructor, single-parameter types with some memory usage and access optimisations. <code haskell> newtype WithIndex a = WithIndex (a, Int) </code> <hidden implementation details about newtype> Let's take the following two types as examples: <code haskell> data A = A Int newtype B = B Int </code> ''data'' constructors are implemented as a set of pointers to the underlying parameters, so an object of type ''A'' is essentially a pointer to an ''Int''. Accessing the underlying ''Int'' requires an indirect memory access, and we need sapce allocated for both the ''Int'' and the pointer. ''newtype'' is represented directly by the underlying type, leading to the elimination of the indirect memory access and removes the additional memory requirement to store the pointer. </hidden> ===== Type Classes ===== Type classes represent a similar concept to Java interfaces. They are used to group together types which have a certain behaviour. For example, all types which can be converted to a ''String'' and printed to the console belong to the ''Show'' type class. <note>Do not confuse type classes with the classes from the Object-Oriented paradigm. Type classes are a category of **types**, while OOP classes are a category of **objects**.</note> We can define a class as such: <code haskell> class MyShow a where myShow :: a -> String </code> To enroll a type in this class we create an instance for it: <code haskell> data BinaryDigit = One | Zero instance MyShow BinaryDigit where myShow One = "1" myShow Zero = "0" </code> Type classes allow us to place restrictions on parameter types in function definitions, other type class definitions and instaces: <code haskell> showTable :: Show a => [[a]] -> String -- this function can show any table-like list-of-lists as long as the type of the elements is showable itself showTable table = ... -- we can freely use 'show' on each "cell" of the table class (Eq a) => Ord a where -- any type 'a' belonging to this class also needs to belong in the 'Eq' class (<), (<=), (>=), (>) :: a -> a -> Bool max, min :: a -> a -> a instance (Show a) => Show [a] where -- we define an instance for lists of all showable types show = ... </code> After the lab, you can take a look at some more cool stuff related to data types in haskell and functional types in general: [[https://wiki.haskell.org/Unboxed_type|Unboxed types]] [[https://www.fpcomplete.com/haskell/tutorial/lens/|Lenses]] ===== 9.1. Tournament ===== We wish to model a tournament scoring system for 1v1 games such as chess (we will not be referring to the logic of the game itself, only the scorekeeping system). The tournament will have two stages: * Group stage: Players are placed into $math[2^n] groups. Each player plays against all other players in the same group. The top 2 players will advance to the elimination stage. * Elimination stage: Players are paired up to play against each other. For each matchup, the loser gets eliminated, the winner remains. The process is repeated until a single player remains. **9.1.1.** Define the datatypes we will need: <code haskell> data MatchResult = ??? -- the result of a match could be a Win, Loss or Draw data Player = Player {???} -- we care about 3 things: the player's name, elo (floating point which measures the player's strength), and a list of games played (more specifically, their results) data Tree a = ??? deriving Show -- A binary tree we will use to represent the elimination stage of the tournament type Tournament = Tree Player </code> **9.1.2.** We want to be able to show a MatchResult. For this, we could do deriving Show to let GHC derive the implementation, but let's do it ourselves. <code haskell> instance Show MatchResult where ??? </code> **9.1.3.** We will want to update player match history after each match. <code haskell> addResult :: MatchResult -> Player -> Player addResult = ??? </code> **9.1.4.** During the group stage of the tournament, we will require a point-based scoring system. A loss is worth 0 points, a win is worth 1, and a draw 0.5. <code haskell> points :: MatchResult -> Float points = ??? </code> **9.1.5.** To determine the best players from each group, we will need to determine how many points each player scored. <code haskell> score :: Player -> Float score = ??? </code> **9.1.6.** Finally, for the group stage we will need to sort the players to establish a ranking. The easiest way to do this is to define a order relationship for players, based solely on score (2 players are equal if their scores are equal, similar for greater than and less than relationships) <code haskell> instance Eq Player where (==) = ??? instance Ord Player where (<=) = ??? -- haskell can infer implementations for all other operations if the implementation for (<=) is given. </code> **9.1.7.** For simplicity, we will 'simulate' matches between players by looking at their elo. The player with more elo always wins. <code haskell> playGame :: Player -> Player -> (Player, Player) playGame player1 player2 = ??? </code> TODO: Simulate the groups and elimination phases. Code skeleton ? Sample players (so they can test things faster).