Important typeclasses
Understanding a typeclass¶
The best way to understand any given typeclass is to find its documentation online, inspect its methods, and look at some instances. This is usually easy with Google, or failing that, Hoogle.
Here is an example, the Semigroup class:

To understand what methods the class requires for its instances, see "minimal complete definition" where (<>) is the only (required) method. See below for its type.
Note
You will also see a comment about associativity, which is a property that instances should have. This property can't be automatically enforced, so is the responsibility of the writer of the instance.
The next step is to inspect some instances, which are also listed below, like:

:info¶
The Haskell repl will also provide useful information:
> :info Num
type Num :: * -> Constraint
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’
Show¶
A class for converting a type into a String, which can be displayed. String is a legacy type, but Show is widely used, and can be derived by Haskell:
Most instances that should exist do exist. For example:
instance Show Intinstance Show Boolinstance Show a => Show [a]instance Show a => Show (Maybe a)
Gotcha
Haskell won't show arbitrary functions, because they don't have a Show instance. This makes sense, since it is impossible to show all the (infinite) input-output pairs of a function.
Eq¶
Types which support a notion of equality.
Ord¶
Types which support a notion of comparison.
Num¶
Types which support a notion of addition, multiplication and negation. Some laws, like commutativity of addition, are expected to hold.
Semigroup¶
Provides a method to combine two values: (<>) :: a -> a -> a
Any instance should define <> such that it is associative (i.e. a <> (b <> c) = (a <> b) <> c)
instance Semigroup Text¶
> :set -XOverloadedStrings -- (1)!
> import Data.Text
> text = "hello"
> text2 = "world"
> text <> text2
"helloworld"
- See here for explanation of why this is needed.
Monoid¶
Info
For historical reasons (Monoid predates Semigroup), Monoid has a method mappend which is redundant given the inheritance of <> from Semigroup.
The Monoid constraint implies the Semigroup constraint.
mempty (short for: monoid empty) is a value of type a.
Examples:
> mempty :: Any
Any {getAny = False}
> mempty :: All
All {getAll = True}
> mempty :: Product Int -- (1)!
Product {getProduct = 1}
> mempty :: Sum Int
Sum {getSum = 0}
> mempty :: [Int]
[]
> mempty :: [Bool]
[]
> mempty :: [a]
[]
> import Data.Text
> mempty :: Text
""
-- if X has a Monoid instance, so does (Y -> X) for any Y.
> (mempty :: Int -> Text) 4
""
> (mempty :: Int -> Text) 6
""
Product Xhas aMonoidinstance ifXhas aNuminstance.
Functor¶
- The kind signature
f :: * -> *requires the GHC2021 standard extensions.
Hint
Types which are instances of Functor must have kind * -> *.
So Int or Bool or Either Int Bool or [Int] cannot be instances of Functor, but Either Int, or [] can. (See section on partial application of types.)
List¶
The definition of fmap for [] is just map
> ls = [1 :: Int, 2, 3]
> :t ls
ls :: [Int]
> fmap even ls
[False,True,False]
> :t fmap even ls
fmap even ls :: [Bool]
Maybe¶
> maybeChar = Just 'a'
> :t maybeChar
maybeChar :: Maybe Char
> fmap (=='a') maybeChar
Just True
> :t fmap (=='a') maybeChar
fmap (=='a') maybeChar :: Maybe Bool
> fmap (=='a') Nothing
Nothing
Either a¶
Note
Either a is Either partially applied to a, and has kind * -> * as required.
> eitherChar = Right 'a'
> :t eitherChar
eitherChar :: Either a Char -- (1)!
> fmap (=='a') eitherChar
Right True
> :t fmap (=='a') eitherChar
fmap (=='a') eitherChar :: Either a Bool
> other = Left True
> :t other
other :: Either Bool b
> fmap (=='a') other
Left True
> :t fmap (=='a') other
fmap (=='a') other :: Either Bool Bool -- (2)!
-
Haskell correctly infers that
acan be any type. -
Haskell correctly infers the type, which is no longer universally quantified.
Reader r¶
So, for example, Reader Int Bool is really just a wrapper around a function Int -> Bool.
Conceptually, think of a Reader env a as a value of type a that has access to (i.e. depends on) a value of type env.
An example:
> import Control.Monad.Reader -- (1)!
> val = reader -- (2)!
(\flag -> if flag then "hello world" else "no greeting")
> runReader val True
"hello world"
> runReader val False
"no greeting"
-- example of fmap
> newVal = fmap (take 5) val
> runReader newVal True
"hello"
- From the
mtlpackage. mtldoesn't defineReaderexactly as shown above, so use lowercasereaderto construct a value of typeReader err a, rather than uppercaseReader.
State s¶
So, for example, State Int Bool is really just a wrapper around a function Int -> (Bool, Int).
Conceptually, think of a State st a as a value of type a that requires a value of type s to be obtained, and results in a new value of type s.
Example:
> val = state (\ls -> if length ls > 3 then (Just (head ls), drop 1 ls) else (Nothing, ls))
> runState val [1,2,3]
(Nothing,[1,2,3])
> runState val [1,2,3,4]
(Just 1,[2,3,4])
-- example of fmap
> import Data.Maybe
> newVal = fmap isJust val
> runState newVal [1,2,3]
(False,[1,2,3])
> runState newVal [1,2,3,4]
(True,[2,3,4])
Applicative¶
class Functor f => Applicative f where
pure :: a -> f a
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
> import Control.Applicative
-- examples with []
> pure 1 :: [Int]
[1]
> liftA2 (+) [1,2,3] [2,3,4]
[3,4,5,4,5,6,5,6,7]
> liftA2 (\x y -> (x,y)) ['a', 'b'] [True, False, True]
[('a',True),('a',False),('a',True),('b',True),('b',False),('b',True)]
-- examples with Maybe
> data Color = Black | White deriving Show
> pure Black :: Maybe Color
Just Black
> liftA2 (+) (Just 3) (Just 4)
Just 7
> liftA2 (+) (Just 3) Nothing
Nothing
> liftA2 (+) Nothing (Just 5)
Nothing
-- examples with Reader
> boringVal = pure True :: Reader Int Bool
> runReader boringVal 4
True
> runReader boringVal 3
True
-- example of liftA2
val = reader (\flag -> if flag then "hello world" else "no greeting")
> combinedVal = liftA2 (<>) val val
> runReader combinedVal True
"hello worldhello world"
> runReader combinedVal False
"no greetingno greeting"
Note
liftA2 and pure can be used to define:
and conversely, <*> and pure can be used to define liftA2. For this reason, pure and <*> are also sometimes given as the basic methods of Applicative.
Monad¶
Note
For legacy reasons, Monad also has a method return, which is a synonym for pure, and is therefore redundant, because all Monad instances are also Applicative instances and so have access to the pure method.
Hint
Compare the type of (>>=) with the type of fmap. In fmap, the function f in fmap f x has type a -> b, but in (>>=), it has type a -> m b.
As a concrete example, consider lists:
fmap :: (a -> b) -> ([a] -> [b])(>>=) :: (a -> [b]) -> ([a] -> [b])
-- lists
> upTo5 c = [c .. 5]
> [1,2,3,4] >>= upTo5
[1,2,3,4,5,2,3,4,5,3,4,5,4,5]
> Just True >>= (\x -> if x then Just 1 else Nothing)
Just 1
> Just False >>= (\x -> if x then Just 1 else Nothing)
Nothing
> Nothing >>= (\x -> if x then Just 1 else Nothing)
Nothing
Using do-notation, the first example above becomes:
An illustrative example with State:
example :: State [Int] Bool
example = do
stack <- get
let headIsGreaterThan3 = head stack > 3
if headIsGreaterThan3
then put (tail stack)
else pure ()
return headIsGreaterThan3
-- > runState example [1,2,3]
-- (False,[1,2,3])
-- > runState example [4,2,3]
-- (True,[2,3])
Alternative¶
This is like Monoid but for a value of type f a, where f is an instance of Applicative. Parsers are a common use case.
Another is backtracking search which uses the Alternative instance of the Logic monad.
Other instances include Maybe and [].
Created: January 11, 2023