summaryrefslogtreecommitdiff
path: root/lec02/notes.md
blob: ed162fc501b50551cef248c6bea0cc351a6dc241 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Lecture 2: Algebraic data types

## Enumeration types

	data Thing = Shoe
        	   | Ship
		   | SealingWax
		   | Cabbage
		   | King
	derivating Show

The example above is the **type declaration** of an **enumeration type**. The new type is called `Thing` and it has 5 **data constructors** (`Shoe`, `Ship`, etc.). A type's constructors are the only values for that type.

The line that says `deriving Show` is a special instruction that tells the GHC to automatically generate default code for converting a `Thing` to `String`.

	Shoe :: Thing
	Shoe = Shoe

	listO'Things :: [Thing]
	listO'Things = [Show, SealingWax, King, Cabbage, King]

Functions on enumeration types can be written using pattern-matching.

	isSmall :: Thing -> Bool
	isSmall Show		= True
	isSmall Ship		= False
	isSmall SealingWax	= True
	isSmall Cabbage		= True
	isSmall King		= False

Since function clauses are checked from top to bottom, `isSmall` can be rewritten like this:

	isSmall2 :: Thing -> Bool
	isSmall2 Ship	= False
	isSmall2 King	= False
	isSmall2 _	= True

## Beyond enumerations

Enumeration types are just a special case of Haskell's **algebraic data types**. An example of ADT that is not an enumeration type is the following `FailableDouble`:

	data FailableDouble = Failure
			    | OK Double
	deriving Show

`FailableDouble` has two constructors: `Failure` and `OK`. The former takes no arguments, so it's a value by itself (of type `FailableDouble`). The latter takes a value of type `Double` and `OK` needs it in order to be a value, otherwise it's not.

	ex01 = Failure
	ex02 = OK 3.4

What's the type of `OK`? (TODO: give an answer)

	safeDiv :: Double -> Double -> FailableDouble
	safeDiv _ 0 = Failure
	safeDiv x y = OK (x / y)

It's possible to give names to variables in constructors when using pattern-matching.

	failureToZero :: FailableDouble -> Double
	failureToZero Failure = 0
	failureToZero (OK d)  = d

Data constructors can have more than one argument.

	data Person = Person String Int Thing
	deriving show

	brent :: Person
	brent = Person "Brent" 31 SealingWax

	stan :: Person
	stan = Person "Stan" 94 Cabbage

	getAge :: Person -> Int
	getAge (Person _ a _) = a

Type constructor and data constructors can have the same name, in this case `Person`. However, they live in different name spaces and are different. It's a common idiom to use the type's name for its data constructor when it only has one constructor.

## Algebraic data types in general

Algebraic data types can have one or more constructors, and each of them can have arguments, from zero to as many as we want. The syntax is:

	data AlgDataType = Constr1 Type11 Type12
			 | Constr2 Type21
			 | Constr3 Type31 Type32 Type33
			 | Constr4

Each constructor can be used to create a value of the `AlgDataType` type and, depending on the one that's chosen, a value of type `AlgDataType` may have more separate values and different types for them.

Type and data constructor names must always start with a capital letter, while variables and functions must always start with a lowercase letter.

## Pattern-matching

Although we already went through pattern-matching a bunch of times, it's time to see how it works in general. The idea is that pattern-matching analyzes a value by the constructor it was made with. Different constructors, different things to do, and constructors are the only way to make a decision.

	foo (Constr1 a b)   = ...
	foo (Constr2 a)     = ...
	foo (Constr3 a b c) = ...
	foo Constr4         = ...

Haskell requires parentheses around a constructor which takes a value or more. Constructor values can be given names, which is the first step in order to use such values for computation.

In addition:

  1. Underscores match everything, they are called **wildcard pattern**.
  2. Patterns like `x@pat` both give a name (`x`) to the pattern (`pat`) and match a value against that pattern.
  3. Nested patterns are possible.

Literal values can be thought as themselves being a constructor. Things like `2` or `'c'` are constructors without arguments. This makes it possible to use pattern-matching against literal values.

## Case expressions

**Case expressions** are fundamental for pattern-matching.

	case exp of
		pat1 -> exp1
		pat2 -> exp2

The evaluation of the case construct is similar to that of functions: the given expression (`exp`) is matched against the given patterns in order (`pat1`, `pat2`, etc.), the first match is chosen, and the whole case expression evaluates to the expression associated with the matching pattern. The syntax for defining functions is actually just a convenient way to define case expressions.

Theoretically, as with functions, multiple patterns can match the given expression, but only the first that matches (from the top) is chosen.

## Recursive data types

Data types can be recursive: defined in terms of themselves. Lists are an example: they can be either empty, or a single element followed by the rest of the list. The list type could be defined like this:

	data IntList = Empty | Cons Int IntList

Haskell's actual lists are similar, but they use a special syntax.

Recursive functions are very useful to process recursive data types.

	intListProd :: IntList -> Int
	intListProd Empty      = 1
	intListProd (Cons x l) = x * intListProd l

Binary trees can also be defined with recursive data types. The following type `Tree` is a binary tree with an `Int` at each node and a `Char` at each leaf.

	data Tree = Leaf Char
		  | Node Tree Int Tree
	deriving Show

It can be used as follows:

	tree :: Tree
	tree = Node (Leaf 'x') 1 (Node (Leaf 'y') 2 (Leaf 'z'))