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 10. 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 type 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]] ===== 10.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 m 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. **10.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 results of past games (call it matchHistory) data Tree a = ??? deriving Show -- A binary tree we will use to represent the elimination stage of the tournament </code> **10.1.2.** We want to be able to show a MatchResult and a Player. For this, we could do deriving Show to let GHC derive the implementations, but let's do it ourselves. <code haskell> instance Show MatchResult where ??? instance Show Player where ??? </code> **10.1.3.** We will want to update player match history after each match. <code haskell> addResult :: MatchResult -> Player -> Player addResult = ??? </code> **10.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> **10.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> **10.1.6.** 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> **10.1.7.** For simplicity, we will 'simulate' matches between players by looking at their elo. The player with more elo always wins. <code haskell> -- return the players, updated (used for group stages) playGame :: Player -> Player -> (Player, Player) playGame = ??? -- ex : playGame (Player "A" 1 []) (Player "B" 2 []) == (Player "A" 1 [Loss], Player "B" 2 [Win]) -- return the winner, and the players, all updated (used for elimination stage). In case of a draw, return any of the players instead of the winner. playGameWithWinner :: Player -> Player -> (Player, (Player, Player)) playGameWithWinner = ??? -- ex : playGameWithWinner (Player "A" 1 []) (Player "B" 2 []) == ((Player "A" 1 [Loss], Player "B" 2 [Win])) </code> **10.1.8.** You are given a player and a list of players, simulate a match between the player and each of the other ones. Return the player and the list of players updated with the results of the matches. <code haskell> playAll :: Player -> [Player] -> (Player, [Player]) playAll = ??? </code> **10.1.9.** Now, to simulate the group stage: <code haskell> {- Simulate a single group. Each player will play against all other players exactly once. Return a list of all players, updated to reflect the results of the matches. Hint: use playAll -} playGroup :: [Player] -> [Player] playGroup players = ??? {- Select the best 'm' players from the given group. (Assume the players already have their match history updated). Return 2 lists: one containing the players which were selected and the other containing all other players. hint: sort -} selectPlayers :: [Player] -> Int -> ([Player], [Player]) selectPlayers players m = ??? {- Given a list of groups, simulate their matches and select the best 'm' players from each, and return 2 lists: the selected players and the rest. -} playGroups :: [[Player]] -> Int -> ([Player], [Player]) playGroups groups m = ??? </code> **10.1.10.** Now, we play the elimination phase (you can assume that the number of players is a power of two, so we have a full binary tree): Given a list of players, turn each of them into a binary tree node. Then, until the list contains a single node, group all nodes in the list in pairs, simulate a match between the players at their roots, and replace each pair with a binary tree containing the winning player in the root and the two original trees as children. Only the top (closest to the root) appearence of each player is required to have up-to-date match history. <code haskell> playElimination :: [Player] -> Tree Player playElimination = ??? </code> **10.1.11.** While the ''Tree'' representation may be useful, we will require the final ranking to be in a list format. For each player, keep only the top entry (which will be up-to date in terms of match history). (you can consider all player names to be unique). <code haskell> eliminationResults :: Tree Player -> [Player] eliminationResults = ??? </code> **10.1.12.** Finally, it's time to put everything together: * split the players into ''m'' groups (you choose how this is done) * simulate the group stage, selecting ''n'' players from each group * simulate the elimination stage * return a ranking of all players, sorted in order of descending scores note: ''n'' and ''m'' should be powers of 2 to properly create the elimination phase. <code haskell> runTournament :: [Player] -> Int -> Int -> [Player] runTournament players n m = ??? </code> ===== Example Players ===== To help you in testing, here is a list of randomly-generated players: <code haskell> players :: [Player] players = [ Player {name = "Jill Todd", elo = 69.32222, matchHistory = []}, Player {name = "Cara Wong", elo = 68.451675, matchHistory = []}, Player {name = "Travis Dunlap", elo = 49.667397, matchHistory = []}, Player {name = "Adam Mills", elo = 65.36233, matchHistory = []}, Player {name = "Josephine Barton", elo = 14.974056, matchHistory = []}, Player {name = "Erica Mendez", elo = 27.466717, matchHistory = []}, Player {name = "Derrick Simmons", elo = 11.790775, matchHistory = []}, Player {name = "Paula Hatch", elo = 80.039635, matchHistory = []}, Player {name = "Patricia Powers", elo = 61.08892, matchHistory = []}, Player {name = "Luke Neal", elo = 65.933014, matchHistory = []}, Player {name = "Jackie Stephenson", elo = 86.00121, matchHistory = []}, Player {name = "Bernice Nixon", elo = 2.8692048, matchHistory = []}, Player {name = "Brent Cobb", elo = 39.80139, matchHistory = []}, Player {name = "Bobbie Sanderson", elo = 81.07552, matchHistory = []}, Player {name = "Zachary Conner", elo = 63.88572, matchHistory = []}, Player {name = "Shawn Landry", elo = 7.68082, matchHistory = []}, Player {name = "Mabel Gentry", elo = 88.13421, matchHistory = []}, Player {name = "Enrique Ali", elo = 9.568502, matchHistory = []}, Player {name = "Clara McLaughlin", elo = 60.83427, matchHistory = []}, Player {name = "Jacqueline Connell", elo = 60.091232, matchHistory = []}, Player {name = "Jared Morgan", elo = 49.84152, matchHistory = []}, Player {name = "Lorraine Castaneda", elo = 34.701054, matchHistory = []}, Player {name = "Robin Hurd", elo = 78.33226, matchHistory = []}, Player {name = "Vince Dunlap", elo = 63.634525, matchHistory = []}, Player {name = "Elaine Winter", elo = 34.86934, matchHistory = []}, Player {name = "Bennie Godfrey", elo = 73.81608, matchHistory = []}, Player {name = "Gale Britton", elo = 16.05768, matchHistory = []}, Player {name = "Jeanne Mathis", elo = 34.603416, matchHistory = []}, Player {name = "Aida Greenwood", elo = 8.308169, matchHistory = []}, Player {name = "Christian Witt", elo = 80.397675, matchHistory = []}, Player {name = "Cecelia Dyer", elo = 80.657974, matchHistory = []}, Player {name = "Edwin Gallagher", elo = 14.976497, matchHistory = []}] </code>