This is an old revision of the document!
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.
type Matrix = [[Int]] type Name = String type PhoneNumber = String PhoneBook = Map Name PhoneNumber
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).
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
To create objects of the given types, we use the constructors as if they were functions:
bDigitOne :: BinaryDigit bDigitOne = One maybeFive :: Maybe Int maybeFive = Just 5 myIntList :: List Int myIntList = Cons 1 (Cons 2 Void)
Pattern matching can be used on any types defined with data
.
listHead :: List a -> a listHead (Cons x _) = x listHead Void = undefined
Records
Additionally, we can use records
to give names to specific paraneters of constructors:
data Dog = Dog{ name :: String , breed :: String , age :: String }
Using this syntax allows us to create objects like this:
myDog = Dog{name="Spike", breed="Golden Retriever", age = 5}
Additionally, it defines a 'getter' function for each named field:
name myDog -- "Spike" breed myDog -- "Golden Retriever" age myDog -- 5
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'
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.
newtype WithIndex a = WithIndex (a, Int)
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.
We can define a class as such:
class MyShow a where myShow :: a -> String
To enroll a type in this class we create an instance for it:
data BinaryDigit = One | Zero instance MyShow BinaryDigit where myShow One = "1" myShow Zero = "0"
Type classes allow us to place restrictions on parameter types in function definitions, other type class definitions and instaces:
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 = ...
After the lab, you can take a look at some more cool stuff related to data types in haskell and functional types in general:
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 $ 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:
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
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.
instance Show MatchResult where ???
9.1.3. We will want to update player match history after each match.
addResult :: MatchResult -> Player -> Player addResult = ???
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.
points :: MatchResult -> Float points = ???
9.1.5. To determine the best players from each group, we will need to determine how many points each player scored.
score :: Player -> Float score = ???
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)
instance Eq Player where (==) = ??? instance Ord Player where (<=) = ??? -- haskell can infer implementations for all other operations if the implementation for (<=) is given.
9.1.7. For simplicity, we will 'simulate' matches between players by looking at their elo. The player with more elo always wins.
playGame :: Player -> Player -> (Player, Player) playGame player1 player2 = ???
TODO:
Simulate the groups and elimination phases. Code skeleton ? Sample players (so they can test things faster).