Contents

Top

Haskell Types Part 2

Type classes—function overloading

How Haskell overloads function names like ==, <=, + for various types, and how you can extend them to your own types, and how you can declare your own overloadable function names.

Two aspects:

A “type class” links the two aspects:

Using == and /= for example, the class is called “Eq” in the standard library:

The definition of Eq goes like:

class Eq a where
    (==), (/=) :: a -> a -> Bool

    -- default implementation for (==)
    x == y = not (x /= y)

    -- default implementation for (/=)
    x /= y = not (x == y)

    -- default implementations deliberately circular so an instance just has to
    -- override one of them to break the cycle

“For a type a to be an instance, it must support those functions (of those types)”.

General syntax:

class ClassName typeVar where
  methodName :: type sig containing typeVar
  ...

  -- Optional: default implementations

Bool is an instance, it goes like:

instance Eq Bool where
    -- So a=Bool in this context, i.e.,
    -- (==), (/=) :: Bool -> Bool -> Bool
    False == False    = True
    True == True      = True
    _ == _            = False

    -- default implementation for (/=) takes hold

WARNING:

Method types outside classes look like this:

(==) :: Eq a => a -> a -> Bool

The additional “Eq a =>” is marker for: polymorphic but user's choice of a must be an instance of Eq. This marker also appears when you write polymorphic functions using the methods, e.g.,

-- Determine whether the 3 arguments are mutually equal.  Needs the user-chosen
-- type to be an instance of Eq (supports "==") but otherwise polymorphic.
eq3 :: Eq a => a -> a -> a -> Bool
eq3 x y z = x==y && y==z
in HaskellTypes2.hs

Another example: The “Ord” class groups together <=, <, etc.:

class Eq a => Ord a where
    compare :: a -> a -> Ordering    -- data Ordering = LT | EQ | GT
    (<), (<=), (>), (>=) :: a -> a -> Bool
    max, min :: a -> a -> a

    -- there are default implementations; an instance just has to provide
    -- compare or <=

This “Eq a =>” is a bit different. It means: An instance of Ord is also an instance of Eq. Best understood from two angles:

We say “Ord is a subclass of Eq”, but beware that this is unrelated to OOP subclasses.

-- Sorting algorithms just need Ord but otherwise polymorphic.
insertionSort :: Ord a => [a] -> [a]
insertionSort [] = []
insertionSort (x:xt) = insert x (insertionSort xt)
  where
    insert e [] = [e]
    insert e xs@(x:xt)
        | e <= x = e : xs
        | otherwise = x : insert e xt

-- De-duplicating sorting uses both (==) and (<).  Type sig just needs Ord.
uniqInsertionSort :: Ord a => [a] -> [a]
uniqInsertionSort [] = []
uniqInsertionSort (x:xt) = insert x (uniqInsertionSort xt)
  where
    insert e [] = [e]
    insert e xs@(x:xt)
        | e < x = e : xs
        | e == x = xs
        | otherwise = x : insert e xt
in HaskellTypes2.hs

Recall we defined our own list types:

data MyIntegerList = INil | ICons Integer MyIntegerList
    deriving Show
data MyList a = Nil | Cons a (MyList a)
    deriving Show
in HaskellTypes2.hs

Let's make them instances of Eq so we can use == on them too:

instance Eq MyIntegerList where
    INil == INil              = True
    ICons x xs == ICons y ys  = x==y && xs==ys
    _ == _                    = False

instance Eq a => Eq (MyList a) where
-- The "Eq a =>" there means I need to use (==) on type a.
-- Try omitting it to see what happens.
    Nil == Nil              = True
    Cons x xs == Cons y ys  = x==y && xs==ys
                              -- "x==y" is when we need to assume Eq a.
    _ == _                  = False
in HaskellTypes2.hs

Check out these other type classes: Bounded, Enum (its methods are behind the [1..n] notation), Show, Read.

Reference: Haskell 2010 Section 6.3: Standard Haskell Classes

Often it is straightforward but boring to write instances for these classes, so the computer offers to auto-gen for you. Restrictions apply. You request it at the definition of your algebraic data type like this:

data MyType = ... deriving (Eq, Ord, Bounded, Enum, Show, Read)

For example the computer would write the same == algorithms above for MyIntegerList and MyList.

Reference: Haskell 2010 Chapter 11: Specification of Derived Instances

Number types and classes

Standard library has Int, Integer, Rational, Float (single-precision floating point), Double (double-precision floating point), Complex a (a is Double or Float usually).

Number operations are grouped into several type classes, e.g.,

Num:
some methods: +, -, *, abs
instances: all number types

Integral:
some methods: div, mod
instances: Int, Integer

Fractional:
some methods: /, recip
instances: Rational, Float, Double, Complex a

You can add your own number type by making it an instance of the relevant classes. (E.g., you could have written your own Rational and Complex.)

Reference: Haskell 2010 Section 6.4: Numbers

Exercise: Why is this a type error?

let xs :: [Double]
    xs = [1, 2, 3]
in sum xs / length xs

Answer:
sum xs :: Double
length xs :: Int
(/) wants the two operands to be of the same type.

How to fix: sum xs / fromIntegral (length xs)
or use realToFrac

fromIntegral :: (Integral a, Num b) => a -> b
realToFrac :: (Real a, Fractional b) => a -> b

Example: Machine epsilon.

https://gist.github.com/AndrewBarfield/2557034 is forced to write two copies of the same code for two number types because C# is a stupid language.

This is criminal in this the 21st Century.

(But see https://twitter.com/mosheroperandi/status/856946180810354688 for even worse!)

A satisfactory language allows you to hand in just one copy:

epsilon :: (Ord a, Fractional a) => a
epsilon = let
    -- [1, 0.5, 0.25, ...]
    halves = iterate (/ 2) 1
    -- But only those that satisfy 1+e > 1.
    notTooSmall = takeWhile (\e -> 1 + e > 1) halves
    -- The last such e is the machine epsilon.
    in last notTooSmall

epsilonDouble :: Double
epsilonDouble = epsilon

epsilonFloat :: Float
epsilonFloat = epsilon
in HaskellTypes2.hs

What other statically-typed languages do

C++

Before C++20: Template function prototype does not say which overloaded functions/operators it uses:

template<typename T> List<T> insertionSort(List<T> xs)
{
   ... just go ahead use <= on T values ...
}

But template is like macro. At use site, compiler has access to the insertionSort template code (to expand it right there), can see it uses <=, can check the user-chosen type supports it.

Works, but tight coupling and all its problems: defeats separate compilation, poor error messages.

Since C++20: That, plus concepts, similar to Haskell classes and can do other things.

Java, Scala, Rust, etc.

Type signature states that the type variable has to extend an interface:

<T extends Comparable<T>> List<T> insertionSort(List<T>)

Learned from Haskell type classes but adapted to OOP. (In fact, Java generics came from Phil Wadler who brought it from Haskell parametric polymorphism.)

Good de-coupling and all its benefits (Haskell type classes too):

SML, Caml, OCaml

A module can take other modules as parameters. Real Template Methods.

Put insertionSort into a module—call it Sorter—but it takes as parameter another module that specifies element type and comparator.

(* Example in OCaml *)
module Sorter(Cmp : sig
                      type t
                      val leq : (t,t) -> bool
                    end)
= struct
    let rec insertionSort xs = ...
      (* insertion sort code; use Cmp.leq to compare *)
end

To use Sorter for ints, write a module that contains int comparison operators, use it to instantiate Sorter.

(* Example in OCaml *)
module IntCmp = struct
    type t = int
    let leq (x,y) = x<=y
end

module IntSorter = Sorter(IntCmp)
(* Now can use IntSorter.insertionSort for [int] *)

Similarly for other element types, even alternative orders.