Continuing our series on Haskell, this month, we focus on Haskell classes and user defined data types.
Haskell is a purely functional programming language and it enforces strictness with the use of types. In this article, we shall explore type classes and user defined data types.
Consider the elem function that takes an element of a type, a list, and returns true if the element is a member of the list; and if not, it returns false. For example:
ghci> 2 `elem` [1, 2, 3] True ghci> 5 `elem` [1, 2, 3] False
Its type signature is shown below:
ghci> :t elem elem :: Eq a => a -> [a] -> Bool
The type signature states that the type variable a must be an instance of class Eq. The class constraint is specified after the :: symbol and before the => symbol in the type signature. The elem function will thus work for all types that are instances of the Eq class.
The word class has a different meaning in functional programming. A type class is a parametrised interface that defines functions. A type that is an instance of a type class needs to implement the defined functions of the type class. The Eq class defines functions to assert if two type values are equal or not. Its definition in Haskell is as follows:
class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y)
The keyword class is used to define a type class. This is followed by the name of the class (starting with a capital letter). A type variable (a here) is written after the class name. Two functions are listed in this class for finding if two values of a type are equal or not. A minimal definition for the two functions is also provided. This code is available in libraries/ghc-prim/GHC/Classes.hs in the ghc (Glasgow Haskell Compiler) source code.
The example works for integers 2 and 5 in the example above, because they are of type Int, which is an instance of Eq. Its corresponding definition is given below:
Instance Eq Int where (==) = eqInt (/=) = neInt
The keyword instance is used in the definition followed by the name of the class Eq, and a specific type Int. It uses two primitive functions eqInt and neInt for checking if the given integers are equal or not. The detailed definition is available in libraries/ghc-prim/GHC/Classes.hs in the ghc source code.
There are a number of pre-defined type classes available in the Haskell platform.
The Ord type class denotes types that can be compared. The compare function will need to be implemented by types that want to be instances of this class. The resultant values of compare are GT, LT, or EQ. For example:
ghci> p > q False ghci> 3 > 2 True
Its type class definition is as follows:
class (Eq a) => Ord a where compare :: a -> a -> Ordering (<), (<=), (>), (>=) :: a -> a -> Bool max, min :: a -> a -> a compare x y = if x == y then EQ else if x <= y then LT else GT x < y = case compare x y of { LT -> True; _ -> False } x <= y = case compare x y of { GT -> False; _ -> True } x > y = case compare x y of { GT -> True; _ -> False } x >= y = case compare x y of { LT -> False; _ -> True } max x y = if x <= y then y else x min x y = if x <= y then x else y
The Ord type class needs to be a sub-class of the Eq class because we should be able to test for equality of two values if they need to be compared. This is also defined as a constraint in the class definition. Seven functions are provided and a minimal definition given in the code snippet. The instance definitions for Char and Int types are available from libraries/ghc-prim/GHC/Classes.hs in the ghc source code.
The Enum type class is for types whose values can be listed in an order for which you can find predecessor and successor elements. For example:
ghci> succ a b ghci> pred EQ LT The class definition for Enum is given below: class Enum a where succ :: a -> a pred :: a -> a toEnum :: Int -> a fromEnum :: a -> Int enumFrom :: a -> [a] enumFromThen :: a -> a -> [a] enumFromTo :: a -> a -> [a] enumFromThenTo :: a -> a -> a -> [a] succ = toEnum . (+ 1) . fromEnum pred = toEnum . (subtract 1) . fromEnum enumFrom x = map toEnum [fromEnum x ..] enumFromThen x y = map toEnum [fromEnum x, fromEnum y ..] enumFromTo x y = map toEnum [fromEnum x .. fromEnum y] enumFromThenTo x1 x2 y = map toEnum [fromEnum x1, fromEnum x2 .. fromEnum y]
The instance for type ordering for the Enum class is as follows:
instance Enum Ordering where succ LT = EQ succ EQ = GT succ GT = error Prelude.Enum.Ordering.succ: bad argument pred GT = EQ pred EQ = LT pred LT = error Prelude.Enum.Ordering.pred: bad argument toEnum n | n == 0 = LT | n == 1 = EQ | n == 2 = GT toEnum _ = error Prelude.Enum.Ordering.toEnum: bad argument fromEnum LT = 0 fromEnum EQ = 1 fromEnum GT = 2 -- Use defaults for the rest enumFrom = boundedEnumFrom enumFromThen = boundedEnumFromThen
You can find the definition and instance definitions for Char and Ordering in libraries/base/GHC/Enum.lhs in the ghc source code.
The Show type class lists a show function to display or print data. For example:
ghci> show 3.1415 3.1415 ghci> show True True
The above code works for both Float and Bool because there are instance definitions for each for the Show type class.
The read function for the Read type class takes as input a String and converts it to an appropriate data type, if possible. For example:
ghci> read 1 + 2.0 3.0 ghci> read False || True True
You will find the class definitions and instances for Show and Read in libraries/base/GHC/Show.lhs and libraries/base/GHC/Read.lhs respectively. The .lhs file is a literate Haskell source file in which you can combine both text and code. You can also find the definition for a class, a function or type inside GHCi using :i. For example:
ghci> :i Eq class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool -- Defined in `GHC.Classes instance Eq Integer -- Defined in `integer-gmp:GHC.Integer.Type instance Eq Ordering -- Defined in `GHC.Classes instance Eq Int -- Defined in `GHC.Classes instance Eq Float -- Defined in `GHC.Classes instance Eq Double -- Defined in `GHC.Classes instance Eq Char -- Defined in `GHC.Classes instance Eq Bool -- Defined in `GHC.Classes ... ghci> :i read read :: Read a => String -> a -- Defined in `Text.Read ghci> :i Int data Int = GHC.Types.I# GHC.Prim.Int# -- Defined in `GHC.Types instance Bounded Int -- Defined in `GHC.Enum instance Enum Int -- Defined in `GHC.Enum instance Eq Int -- Defined in `GHC.Classes instance Integral Int -- Defined in `GHC.Real instance Num Int -- Defined in `GHC.Num instance Ord Int -- Defined in `GHC.Classes instance Read Int -- Defined in `GHC.Read instance Real Int -- Defined in `GHC.Real instance Show Int -- Defined in `GHC.Show
Lets suppose you input the following in a GHCi prompt:
ghci> read 3 <interactive>:5:1: No instance for (Read a0) arising from a use of `read The type variable `a0 is ambiguous Possible fix: add a type signature that fixes these type variable(s) Note: there are several potential instances: instance Read () -- Defined in `GHC.Read instance (Read a, Read b) => Read (a, b) -- Defined in `GHC.Read instance (Read a, Read b, Read c) => Read (a, b, c) -- Defined in `GHC.Read ...plus 25 others In the expression: read 3 In an equation for `it: it = read 3
The interpreter does not know what type to convert 3 to, and hence you will need to explicitly specify the type:
ghci> read 3 :: Int 3 ghci> read 3 :: Float 3.0
A type synonym is an alias that you can use for a type. String in the Haskell platform is an array of characters defined using the type keyword:
type String = [Char]
You can also create a new user data type using the data keyword. Consider a Weekday data type that has the list of the days in a week:
data Weekday = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
The data keyword is followed by the name of the data type, starting with a capital letter. After the equal to (=) sign, the various value constructors are listed. The different constructors are separated by a pipe (|) symbol.
If you load the above data type in GHCi, you can test the value constructors:
ghci> :t Monday Monday :: Weekday
Each value constructor can have many type values. The user defined data type can also derive from type classes. Since the primitive data types already derive from the basic type classes, the user defined data types can also be derived. Otherwise, you will need to write instance definitions for the same. The following is an example for a user data type Date that derives from the Show type class for displaying the date:
data Date = Int String Int deriving (Show)
Loading the above in GHCi, you get:
ghci> Date 3 September 2014 Date 3 September 2014
The above code will work even if we swap the year and day, because the syntax is correct but the semantics are not!
ghci> Date 2014 September 3 Date 2014 September 3
You can also use the record syntax that can give you helper functions:
data Date = Date { day :: Int , month :: String , year :: Int }
This gives you three helper functions to retrieve the day, month and year from a Date.
ghci> let d = Date {day = 14, month = September, year = 2014} ghci> day d 14 ghci> month d September
You can also make data type definitions more explicit with types:
data Date = Date Day Month Year deriving (Show) type Year = Int type Day = Int data Month = January | February | March | April | May | June | July | August | September | October | November | December deriving (Show)
Loading the above in GHCi, you can use:
ghci> Date 3 September 2014 Date 3 September 2014
To support printing the date in a specific format, you can implement an instance for the Show type class. You can also add a check to ensure that the day is within a range, and the year and day cannot be swapped:
instance Show Date where show (Date d m y) | d > 0 && d <= 31 = (show d ++ ++ show m ++ ++ show y) | otherwise = error Invalid day
Load the code in GHCi, and run the following:
ghci> show (Date 3 September 2014) 3 September 2014 ghci> show (Date 2014 September 2) *** Exception: Invalid day
Suppose you wish to support different Gregorian date formats, you can define a data type GregorianDate as follows:
data GregorianDate = DMY Day Month Year | YMD Year Month Day
You can also define your own type classes for functions that define their own behaviour. For example, if you wish to dump the output of a date that is separated by dashes, you can write a Dashed class with a dash function.
class Dashed a where dash :: a -> String instance Dashed Date where dash (Date d m y) = show d ++ - ++ show m ++ - ++ show y
Testing the above in GHCi will give the following output:
ghci> dash (Date 14 September 2014) 14-September-2014
Haskell allows you to define recursive data types also. A parametrised list is defined as:
data List a = Empty | Cons a (List a) deriving (Show)
Lists for the above definition can be created in GHCi, using the following commands:
ghci> Empty Empty ghci> (Cons 3 (Cons 2 (Cons 1 Empty))) (Cons 3 (Cons 2 (Cons 1 Empty)))