summaryrefslogtreecommitdiff
path: root/lec01/notes.md
blob: 40fa874220ad2339515c45da44b5c9e19fcf24d8 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# Lecture 1: Haskell basics

## What's Haskell?

Haskell is a programming language created in the late 1980's by academics. It is lazy and functional, but so were many programming languages at that time. Haskell was born from the necessity of a language that could easily communicate ideas, which was difficult with all those existing languages.

Four fundamental concepts shape the Haskell programming language.

Haskell is **functional**: functions are said to be *first-class* which, in other words, means that they are treated and can be used as other values. Another aspect of this is that Haskell programs mainly *evaulate expressions*, instead of *executing instructions*.

Haskell is **pure**: no value can mutate, no expression can have side effects, and functions evaluated with the same arguments produce the same output.

Haskell is **lazy**: the evaluation of expressions is delayed until their result is used. This makes it possible to, for example, work with infinite data structures.

Haskell is **statically typed**: every expression has a type and all types are checked during compilation, not run-time.

## Themes of the course

### Types

In other programming languages, static types are often annoying to work with. However, Haskell's type system is much more flexible, expressive, and readable. It helps reasoning about the program's structure and design, it documents code, and it translates potential run-time errors in compile-time errors.

### Abstraction

Haskell does a good job when it comes to abstraction, its features make repetitions very infrequent. Some of those features are: parametric polymorphism, higher-order functions, and type classes.

### Wholemeal programming

The idea of wholemeal programming is to *think big*, to think of something in its entirety rather than focusing on every piece that composes it. The problem is first solved at a distance by identifying the expression that makes a general solution. Then, the thinking shifts to the cases that don't behave like the rest.

## First steps in the language

### Declarations, variables, and comments

	x :: Int
	x = 3
	-- This is a single-line comment
	{- This is a
		multi-line comment -}

What does the code above do? It first declares a variable named `x` with type `Int` (`::` is pronounced as "has type"). Then, its value is set to 3. The variable `x` will keep this value forever, until the program is no longer running. Trying to assign another value to `x` later will result in the compiler giving an error.

As we said above, variables are immutable and therefore they are just names for values. In fact, in the program above, `x` was not actually *assigned* a value, it was *defined as* a value.

### Basic types

Haskell basic types can be summed up like this:

- `Int` is a machine-sized integer
- `Integer` is an arbitrary-precision integer
- `Double` is a double-precision floating point
- `Bool` is a boolean number (`True`, `False`)
- `Char` is a unicode character (delimited by single quotes)
- `String` is a list of characters (delimited by double quotes)

The exact dimension of `Int` depends on the machine. However, they are guaranteed by the language to be at least 32 bits in size. The exact range can be found like this:

	biggestInt, smallestInt :: Int
	biggestInt = maxBound
	smallestInt = minBound

On the other hand, `Integer` can be indefinitely large, as long as your available memory permits it.

### GHCi

GHCi is an interactive Haskell prompt or REPL (Read, Eval, Print, Loop) bundled with GHC. It enables the evaluation of expressions by loading Haskell files with `:load` (or `:l`) and `:reload` (or `:r`). It can also provide the type of an expression with `:type` (or `:t`). Typing `:?` lists available commands.

### Arithmetic

Arithmetic operations work similarly to other programming languages: the operators use the [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and they are written with familiar symbols.

One neat feature is that new infix operators can be implemented using just functions with two arguments. Those functions will then need to be wrapped in backticks so that they can be used as infix operators. An example below, which assumes the `mod` function to be defined with two integers as arguments.

	ex1 = mod 19 3
	ex2 = 19 `mod` 3

One downside, however, is that negative numbers need to be wrapped between parentheses, since their "minus" is also an operator and Haskell has no operator precedence.

Another thing to note is that Haskell does not implicitly convert between types. If you have an integer value and our function expects any other type, you can use the `fromIntegral` function. Similarly, if you have a floating point value and you need an integer, there are functions such as `round`, `floor`, and `ceiling`, which have the same meaning as in math. Although this may sound annoying, it's actually useful as implicit conversions are a source of errors.

Another little downside is that the slash symbol (`/`) only performs floating point division and for an integer division you must use the `div` function, optionally as an infix operator.

#### Boolean algebra

Boolean operators include the classical double-ampersand "AND" (`&&`) and the double-vertical-bar "OR" (`||`). The "NOT" operator is simply `not`.

Equality and order comparison follow the usual operators (`==`, `<`, `>=`, etc.) with the exception of the inequality operator, which is made of slash and equal symbols `/=`.

Haskell provides `if` expressions as part of its syntax. They are written as: `if b then t else f`, which evaluates to `t` if `b` is `True`, `f` otherwise. Differently from imperative languages, `else` and the expression that follows it are not optional: the expression must always result in some value. Although Haskell provides `if` expressions, they are not used much in favor of *pattern-matching* and *guards*.

### Function definitions

The following is the most basic way of writing functions.

	sumtorial :: Integer -> Integer
	sumtorial 0 = 0
	sumtorial n = n + sumtorial (n-1)

The first line in a function definition typically defines the types of its arguments and return value. In this case, the function accepts one argument of type `Integer` and returns a value of type `Integer`. If the function accepted two arguments instead of one, its type would have been `Integer -> Integer -> Integer`.

Following the function's type definition is a list of cases to match the function's arguments against. In fact, functions in Haskell are written by cases and each case contains a *clause*. Clauses are evaluated in order, from top to bottom, and may contain constant values as well as valid patterns for **pattern matching**. You write clauses between the function name, which is the first word on the line, and the equal sign (`=`). After the first matching clause is found, it is chosen and its expression evaluated.

The expression, which is the part after the equal sign (`=`), usually contains variables defined in the pattern, which are replaced with the argument's value during the function call. However, it's possible to reference external variables or to just put a constant as expression.

For recursive functions, like the example we just analyzed, it's important to always place the base cases on top, unless you want to create an infinite loop.

There is another way of writing functions, using **guards**.

	hailstone :: Integer -> Integer
	hailstone n
	  | n `mod` 2 == 0 = n `div` 2
	  | otherwise      = 3*n + 1

In this example, `n` is our only clause and it has two guards: one checks whether `n` is a factor of two, while the other matches all remaining values using the `otherwise` keyword.

In guards, after entering a clause with matching pattern, conditions are evaluated from top to bottom and the first that evaluates to `True` is chosen. The special keyword `otherwise` is reached when none of the above evaluated to `True` and it always evaluates to `True`. In fact, `True` is the actual value of the `otherwise` keyword.

There can be any number of guards for each clause, but each guard must evaluate to a Boolean expression. Boolean expressions may not depend on any of the function's arguments and just be constant expressions. In case none of the guards provided for a clause is matched, the next clause is considered.

### Pairs

Pairs are simply pairs of values.

	p :: (Int, Char)
	p = (3, 'x')

Values are not required to have the same type, as the example shows. The notation with parentheses and a comma is used for both the type and the value of a pair.

Pairs are valid patterns for pattern matching in clauses and the variable names provided between parentheses take the value of the pair's elements.

	sumPair :: (Int,Int) -> Int
	sumPair (x,y) = x + y

### Functions with many arguments

Functions can have more than one argument. In such cases, the function type grows while the clauses can simply add variables to patterns.

	f :: Int -> Int -> Int -> Int
	f x y z = x + y + z

Since function application has higher precedence over infix operators, infix operations whose result is passed as function argument must be wrapped in parentheses.

	f 3 (n+1) 7

### Lists

Lists are one of the types Haskell supports by default. They are written as their type surrounded by square brackets.

	nums :: [Integer]
	nums = [1,2,3,19]

Haskell provides **list comprehensions**.

Strings are lists of characters, `String` is the same as `[Char]` and it uses a special syntax for its values: instead of writing lists of characters, they are simply written with double quotes. This generalization allows Strings to be processed just like any other list.

Lists can be empty.

	myList = []

By using the **cons operator** (`:`), you can create new lists from existing lists, including the empty list. In fact, the cons operator, provided an element and a list, respectively as first and second operands, produces a new list with that element as first, followed by all the other elements in the list. For convenience, you can use `[1, 2, 3]` instead of `1 : 2 : 3 : []`.

It's important to note that *lists are not arrays*. Lists are, in fact, singly linked lists under the hood.

### Functions on lists

The pattern matching syntax supports both lists and the cons operator. They are useful for working on lists in functions.

	intListLength :: [Integer] -> Integer
	intListLength [] = 0
	intListLength (x:xs) = 1 + intListLength xs

The function above calculates the length of a list. As you can see, the first clause matches the empty list and returns zero. It makes sense: an empty list has zero elements. The second clause matches the first element `x` in the list, followed by the rest of the list, named `xs`.

The expression associated with the second clause may look tricky at a first look but it's actually really simple: the list of an element `x` followed by a list `xs` is 1 plus the length of the list `xs`. In other words, the length of a list of `n` elements is just 1 plus the length of the same list with the first element removed. The process of removing the first element of the list and recursively evaluating the list again is repeated until the remaining list has length zero. The result of the recursive expression is something like `1 + 1 + ... + 0`.

Here, the variable `x` is not used at all. It's useless and it has no reason to exist. In such cases, variable names can be replaced with underscores. It's a special syntax to tell Haskell to not bother providing a name for that value.

	intListLength (_:xs) = 1 + intListLength xs

### Nested patterns

Patterns *can* be nested. This allows you to write more powerful pattern matching expressions.

For example, if, instead of just the first element of a list, you wanted to get the value of the first two, you can write `(x:(y:ys))` instead of `(x:xs)`. Actually, you don't need extra parentheses: `(x:y:ys)`.

### Combining functions

It's smart and elegant to build complex functions by combining simple ones. Thanks to Haskell's lazy evaluation, it is possible to compute and use memory just "as much as needed".

In fact, a function that creates a list, does some computations on it, and then returns a value, only creates as many list elements as needed for the computation. This happens because every element of said list is generated as soon as it's used. The calculations made on the list do not start as soon as the list has been fully generated, but rather after the first element has been generated.