====== Lab 9. Algebraic Data Types ====== ===== 9.0. Some theory ===== Types are an essential component in haskell. They start with capital letters, like ''Int''. We have multiple ways of customizing data types. \\ \\ The first one is type aliases, signaled by the keyword ''type''. These allow us to rename complex types to simpler types. They are used for better clarity. type Matrix = [[Int]] type StudentName = String The second one is ''data''. This enables us to create new data types. These types can take parameters. Using ''|'' allows us to define a sum type (a type with multiple constructors). Take a look at ''Maybe a'' (this is a type that already exists in haskell). We can do a lot of things with these, including pattern matching. 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 A particular case of data is ''newtype''. This is an optimization for single-parameter single-constructor types. Without going into details, this is used to better expose the contained type. We can think of data constructors like pointers (that's how they are implemented). Newtype replaces the pointer with the uderlying value, leading to less memory access and increased performance. newtype WithIndex a = WithIndex (a, Int) For practical usecases, we might need a way to model more complex data. In other languages, this can be achieved using ''structs'', ''classes'', ''interfaces'' etc. Haskell allows us to model data using ''records''. A record is a specific type of data very similar to a struct. We use ''deriving Show'' as a way to be able to convert the type to string when printing it in the terminal. data Dog = Dog{ name :: String , breed :: String , age :: String } deriving Show Now, we can create a Dog like this. myDog = Dog {name="Spike", breed="Golden Retriever", age = 5} How can we access the dog's information? Simple. Haskell implements getters for us by default name myDog --"Spike" breed myDog --"Golder Retriever" age myDog --5 Typeclasses are an important concept in haskell. They are very similar in concept to Java interfaces (we implement certain behaviours through them). For example, we might want to convert an entity to a string to print it. This could be done by implementing the Show class. Take a look at its [[https://hackage.haskell.org/package/base-4.18.0.0/docs/src/GHC.Show.html#Show | implementation]]. Notice the ''{-# MINIMAL showsPrec | show #-}''. That tells us that it's enough to implement one of those functions to have a complete implementation. Let's implement show for ''Maybe a'' -- The syntax "Show a =>" is called a constraint. This tells us that a also needs to be an instance of Show. Why do we need it here? instance Show a => Show (Maybe a) where show Nothing = "Nothing" show (Just x) = "Just " ++ show x In summary, typeclasses allow us to abstract certain behaviours. Some important ones we'll use in this lab are ''Eq''(to specifiy whether 2 things are equal or different) or ''Ord'', to be able to compare type instances. 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. Chess Basics ===== **9.1.1** Let's implement a ''ChessResult'' data type. In chess, a game result can be either a Win, a Draw or a Loss. data ChessResult = ??? **9.1.2** We want to be able to show a ''ChessResult''. For this, we could do ''deriving Show'' to let GHC derive the implementation, but let's do it ourselves. instance Show ChessResult where ??? **9.1.3** In chess, your performance is rated based on a point system. A win is equivalent to a point, a draw is 0.5 points and a loss is 0 points points :: ChessResult -> Float points = ??? **9.1.4** We want to find out the number of point a player earns after playing multiple games. Try to implement this as a closure. score :: [ChessResult] -> Float score = ??? **9.1.5** Now that we can measure a player's score, we want to establish a ranking. Return the descending list of scores. Hint: ''sort'' ranking :: [[ChessResult]] -> [Float] ranking = ??? **9.1.6** Each tournament also offers a cash prize, based on player performance. Compute the earnings for each player. The formula we'll use is ''player_earnings = player points * prize money / total points'' earnings :: [[ChessResult]] -> Float ->[Float] earnings playerScores prizeMoney = ??? ===== 9.2. Chess Tournament ===== **9.2.0** We want to model a chess player using records. We care about 3 things: name, elo(a floating point value which measures a player's strength) and a list of games played (name it tournamentGames). We also want to model a chess tournament. We'll represent it as a binary tree. data Player = Player { ??? } data Tree a = ??? deriving Show type Tournament = Tree Player **9.2.1** We want to be able to compare players based on their performance. In order to do this, we'll have to implement 2 type classes -- Two players are considered equal if their score is the same. Alternatively, 2 players are different if their scores are different instance Eq Player where ??? -- In the same manner, players can be compared based on their score instance Ord Player where ??? **9.2.2** After a game, we want to update a player's games with the new result. addResult :: ChessResult -> Player -> Player addResult = ??? **9.2.3** It's finally time to play some chess. After the game is played, we want to return 2 players with updated results. The first one should be the winner (helpful later). However, everyone knows chess is a luck-based game. As such, a player will win a game if their odds of winning (calculated based on elo difference) is greater than 0.5. -- odds that A beats B based on their elo winningOdds :: Float -> Float -> Float winningOdds eloA eloB = 1 / (1 + 10** ((eloB-eloA)/400)) playGame :: Player -> Player -> (Player, Player) playGame player1 player2 = ??? **9.2.4** A guy with a lot of free time decided it would be fun to play games against multiple opponents. Return the updated player and the updated list of opponents (take a look at ''mapAccumL''). multipleMatches :: Player -> [Player] -> (Player, [Player]) multipleMatches = ??? **9.2.5** It seems that the chess community is boiling and players are ready to throw hands. Each player must face all other players in a single match. At the end, all players will have their results updated. groupStage :: [Player] -> [Player] groupStage = ??? **9.2.6** Playing every player is a bit boring. We want to organize a tournament to decide the best player faster. We can assume each tournament will have 2^n players for simplicity. Each round, groups of 2 players will face each other, with the winner advancing to the next round. Don't forget to update their results. tournamentStage :: [Player] -> Tournament tournamentStage = ??? **9.2.7** Carlos Magnusen wants to organize a chess tournament. As he only wants the best players to participate, he decided to organize a big group stage with all players facing each other a tournament stage with the best 2^n groups players (n must be as high as possible - if 150 players are playing, we expect n to be 7). chessTournament :: [Player] -> Tournament chessTournament = ??? **9.2.8** Carolina wants to see how her beloved Romeo did. However, he sometimes goes to the 'chess' club instead of playing, so he may have not been present at all. Implement the function ''findPlayerScoreByName'', which returns ''Just score'' if he was there or ''Nothing'' if he wasn't findPlayerScoreByName :: String -> Tournament -> Maybe Float findPlayerScoreByName = ???