summaryrefslogtreecommitdiff
path: root/lec05/notes.md
blob: 700fa230a9eb7029810832c108b8e58fb9f9da4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# More Polymorphism and type classes

Haskell's polymorphism, which we've seen in previous lectures, is known as *parametric polymorphism* and requires that polymorphic functions must work uniformly for any input type. This requirement has interesting implications.

## Parametricity

Consider the following function:

	f :: a -> a -> a
	f x y = x && y

Turns out that `f` doesn't compile! The type of `f` (`a -> a -> a`) tells us that `f` *promises* that it's going to work for any type of parameters the caller may choose, but this is not true at all! In fact, the boolean AND operator `&&`, which `f` applies to its two parameters *of variable type*, requires two values of type `Bool`! Sure, `a` might be `Bool` in some cases, but it's not guaranteed for all of them.

In addition to that, Haskell does not allow to query the type of a certain parameter of variable type, because types are *erased* when a Haskell program is compiled. So it's not possible to decide what to do based on the value of a type variable.

This style of polymorphism is called **parametric polymorphism**. A function, like `f`, is said to be *parametric in* the type `a` if the type of `f` contains the type variable `a`. In other words, `f` is supposed to *work uniformly* for any type chosen for `a`.

Turns out that the only two possible functions with the signature `a -> a -> a`, like our `f`, are only two:

	f1 :: a -> a -> a
	f1 x y = x

	f2 :: a -> a -> a
	f2 x y = y

## Two views on parametricity

Differently from other languages, Haskell does not permit to decide what to do based on the type of a value, as we said already. Although this may seem annoying at first, as *writers* of polymorphic functions, it's actually pretty useful as *users* of such functions as it gives us *guarantees* about their behavior. It's much easier to use and reason about our tools if we have strong guarantees on how they behave.

Though this holds true, sometimes it's really handy to just have the ability to decide what to do based on types. Addition is an example! In Haskell, the addition operator `(+)` works for the types: `Int`, `Integer`, and `Double`, but adding two `Int`s is completely different from adding two `Doubles`! Let's see the type of `(+)`:

	Prelude> :t (+)
	(+) :: Num a => a -> a -> a

What's `Num a =>`?

## Type classes

`Num`, as well as `Eq`, `Ord`, and `Show`, are called **type classes**. Functions which make use of them are called **type-class polymorphic**. Type classes are *sets of types* with common functions implemented. Each type class defines a set of functions and, in order for a type to be part of such type class, it must define an implementation for all functions in the type class.

Type class-polymorphic functions work only with types part of one or more type classes. This explains what we said before: although type classes may look like restrictions, it's the only way for a function to have the *guarantee* that all the types it could receive, at the function's user choice, have themselves certain functions defined. A type class required by a function is called a **type class constraint**.

Let's take a look at the `Eq` type class:

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

`Eq` is a type class with a single parameter: `a`. All types which want to be an instance of `Eq` must define two functions, `(==)` and `(/=)`, with the given signatures. `Int`, which is part of this type class, have a definition for these two functions.

Let's look at the type of `(==)`:

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

Do you notice something? `(==)` has the same type constraint as the type class which defines it (and requires its implementation). Why's that? It's simple: `Eq` defines `(==)`, but this function can only be used with types that implement `Eq`, because there is no implementation to follow otherwise. Therefore, type class functions always have a type constraint of type class they come from, in their signature.

When `(==)` is used, like any type class method, the compiler uses type inference to figure out which implementation of the function should be chosen, based on the inferred types of its arguments. It works similarly to overloaded methods in Java. There cannot be more than one type class with the same function name and signature: in that case, the compiler could not decide which one should be called!

We can make our own type and declare an instance of `Eq` for it:

	data Foo = F Int | G Char

	instance Eq Foo where
		(F i1) == (F i2) = i1 == i2
		(G c1) == (G c2) = c1 == c2
		_ == _ = False

		foo1 /= foo2 = not (foo1 == foo2)

It's annoying to have to define both `(==)` and `(/=)`... But we don't need to! Type class functions will be given a *default implementation* in terms of other functions when a custom implementation is not given. It's recommended to use default implementations as much as possible, unless we need a specific behavior, as it reduces the amount of code we need to write.

When we talk about default implementations, we imagine something like this:

	class Eq a where
		(==) :: a -> a -> Bool
		(/=) :: a -> a -> Bool
		x /= y = not (x == y)

It makes sense, but there is an issue: what if, instead of writing only an implementation for `(==)`, I wanted to implement only `(/=)`? With this class I can't: I would overwrite the implementation for `(/=)` and `(==)` would keep being unimplemented!

	class Eq a where
		(==) :: a -> a -> Bool
		(/=) :: a -> a -> Bool
		x == y = not (x /= y)
		x /= y = not (x == y)

This solves the issue: we can define *either* `(==)` or `(/=)` and our implementation of the type class is still going to be correct. We can choose whichever is more convenient for our case, but if we choose none we get infinite recursion!

It turns out that `Eq`, and some other standard type classes, are special: the GHC can automatically generate instances of these type classes for us!

	data Foo' = F' Int | G' Char
		deriving (Eq, Ord, Show)

The `deriving` part tells the GHC to automatically derive instances of `Eq`, `Ord`, and `Show` for our data type `Foo`.

### Type classes and Java interfaces

Type classes are a concept similar to Java interfaces. However:

1. In Java, all interfaces implemented by a class must be declared in such class, while Haskell type class instances are declared separately from the corresponding type and can even live in a separate module.
2. The types that can be used in type class functions are more general and flexible than signatures that can be given to Java interface methods:
   - In multi-parameter type classes, functions from those type classes need *multiple dispatch*, because the implementation depends on the type of all type variables. There is no easy way to do this in Java.
   - Haskell type classes can easily handle binary (ternary, etc.) methods while there is no easy way to do this in Java: one of the two arguments' class needs to have the privilege of getting the method invoked on, like `(+)`, and this asymmetry is awkward.
   - In Java, getting two arguments of a certain interface type does not guarantee that they are actually the same type and it usually requires some type checking.

### Standard type classes

Here are the most common standard type classes:

- `Ord`: elements that can be *totally ordered*, which means that they can be compared to see which is less than the other.
- `Num`: elements of *numeric types*, which support addition, subtraction, and multiplication. Integer literals are type class polymorphic, so they can be used as `Int`, `Integer`, `Double`, or any other type which is an instance of `Num`.
- `Show`: elements which can be converted *into* `String`s (dual of `Read`).
- `Read`: elements which can be converted *from* `String`s (dual of `Show`).
- `Integral`: elements which are whole number types such as `Int` or `Integer`.