Skip to content

Latest commit

 

History

History
794 lines (531 loc) · 31.8 KB

File metadata and controls

794 lines (531 loc) · 31.8 KB
 
Dec 2, 2016
Dec 2, 2016
1
# Functions and Records
2
3
## Chapter Goals
4
5
This chapter will introduce two building blocks of PureScript programs: functions and records. In addition, we'll see how to structure PureScript programs, and how to use types as an aid to program development.
6
7
We will build a simple address book application to manage a list of contacts. This code will introduce some new ideas from the syntax of PureScript.
8
Sep 25, 2019
Sep 25, 2019
9
The front-end of our application will be the interactive mode PSCi, but it would be possible to build on this code to write a front-end in JavaScript. In fact, we will do exactly that in later chapters, adding form validation and save/restore functionality.
Dec 2, 2016
Dec 2, 2016
10
11
## Project Setup
12
13
The source code for this chapter is contained in the file `src/Data/AddressBook.purs`. This file starts with a module declaration and its import list:
14
15
```haskell
Aug 7, 2021
Aug 7, 2021
16
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:imports}}
Dec 2, 2016
Dec 2, 2016
17
```
18
19
Here, we import several modules:
20
Jul 17, 2023
Jul 17, 2023
21
- The `Prelude` module, which contains a small set of standard definitions and functions. It re-exports many foundational modules from the `purescript-prelude` library.
Dec 2, 2016
Dec 2, 2016
22
- The `Control.Plus` module, which defines the `empty` value.
Jul 10, 2023
Jul 10, 2023
23
- The `Data.List` module, provided by the `lists` package, which can be installed using Spago. It contains a few functions that we will need for working with linked lists.
Dec 2, 2016
Dec 2, 2016
24
- The `Data.Maybe` module, which defines data types and functions for working with optional values.
25
Jul 17, 2023
Jul 17, 2023
26
Notice that the imports for these modules are listed explicitly in parentheses (except for `Prelude`, which is typically imported as an open import). This is generally a good practice, as it helps to avoid conflicting imports.
Dec 2, 2016
Dec 2, 2016
27
Oct 22, 2019
Oct 22, 2019
28
Assuming you have cloned the book's source code repository, the project for this chapter can be built using Spago, with the following commands:
Dec 2, 2016
Dec 2, 2016
29
30
```text
31
$ cd chapter3
Oct 22, 2019
Oct 22, 2019
32
$ spago build
Dec 2, 2016
Dec 2, 2016
33
```
34
35
## Simple Types
36
Jul 10, 2023
Jul 10, 2023
37
PureScript defines three built-in types corresponding to JavaScript's primitive types: numbers, strings, and booleans. These are defined in the `Prim` module, which is implicitly imported by every module. They are called `Number`, `String`, and `Boolean`, respectively, and you can see them in PSCi by using the `:type` command to print the types of some simple values:
Dec 2, 2016
Dec 2, 2016
38
39
```text
Oct 22, 2019
Oct 22, 2019
40
$ spago repl
Dec 2, 2016
Dec 2, 2016
41
42
> :type 1.0
43
Number
44
45
> :type "test"
46
String
47
48
> :type true
49
Boolean
50
```
51
Jul 10, 2023
Jul 10, 2023
52
PureScript defines other built-in types: integers, characters, arrays, records, and functions.
Dec 2, 2016
Dec 2, 2016
53
54
Integers are differentiated from floating point values of type `Number` by the lack of a decimal point:
55
56
```text
57
> :type 1
58
Int
59
```
60
61
Character literals are wrapped in single quotes, unlike string literals which use double quotes:
62
63
```text
64
> :type 'a'
65
Char
66
```
67
68
Arrays correspond to JavaScript arrays, but unlike in JavaScript, all elements of a PureScript array must have the same type:
69
70
```text
71
> :type [1, 2, 3]
72
Array Int
73
74
> :type [true, false]
75
Array Boolean
76
77
> :type [1, false]
Oct 28, 2019
Oct 28, 2019
78
Could not match type Int with type Boolean.
Dec 2, 2016
Dec 2, 2016
79
```
80
Jul 10, 2023
Jul 10, 2023
81
The last example shows an error from the type checker, which failed to _unify_ (i.e., make equal) the types of the two elements.
Dec 2, 2016
Dec 2, 2016
82
83
Records correspond to JavaScript's objects, and record literals have the same syntax as JavaScript's object literals:
84
85
```text
Aug 12, 2017
Aug 12, 2017
86
> author = { name: "Phil", interests: ["Functional Programming", "JavaScript"] }
Dec 2, 2016
Dec 2, 2016
87
88
> :type author
89
{ name :: String
90
, interests :: Array String
91
}
92
```
93
Jul 10, 2023
Jul 10, 2023
94
This type indicates that the specified object has two _fields_: a `name` field with the type `String` and an `interests` field with the type `Array String`, i.e., an array of `String`s.
Dec 2, 2016
Dec 2, 2016
95
96
Fields of records can be accessed using a dot, followed by the label of the field to access:
97
98
```text
99
> author.name
100
"Phil"
101
102
> author.interests
103
["Functional Programming","JavaScript"]
104
```
105
Jul 17, 2023
Jul 17, 2023
106
PureScript's functions correspond to JavaScript's functions. Functions can be defined at the top-level of a file by specifying arguments before the equals sign:
Dec 2, 2016
Dec 2, 2016
107
108
```haskell
Aug 18, 2023
Aug 18, 2023
109
import Prelude -- bring the (+) operator into scope
110
Dec 2, 2016
Dec 2, 2016
111
add :: Int -> Int -> Int
112
add x y = x + y
113
```
114
Jul 10, 2023
Jul 10, 2023
115
Alternatively, functions can be defined inline using a backslash character followed by a space-delimited list of argument names. To enter a multi-line declaration in PSCi, we can enter "paste mode" using the `:paste` command. In this mode, declarations are terminated using the _Control-D_ key sequence:
Dec 2, 2016
Dec 2, 2016
116
117
```text
Aug 18, 2023
Aug 18, 2023
118
> import Prelude
Dec 2, 2016
Dec 2, 2016
119
> :paste
Aug 12, 2017
Aug 12, 2017
120
… add :: Int -> Int -> Int
121
… add = \x y -> x + y
Dec 2, 2016
Dec 2, 2016
122
… ^D
123
```
124
125
Having defined this function in PSCi, we can _apply_ it to its arguments by separating the two arguments from the function name by whitespace:
126
127
```text
128
> add 10 20
129
30
130
```
131
132
## Notes On Indentation
133
134
PureScript code is _indentation-sensitive_, just like Haskell, but unlike JavaScript. This means that the whitespace in your code is not meaningless, but rather is used to group regions of code, just like curly braces in C-like languages.
135
Jul 17, 2023
Jul 17, 2023
136
If a declaration spans multiple lines, any lines except the first must be indented past the indentation level of the first line.
Dec 2, 2016
Dec 2, 2016
137
Jul 10, 2023
Jul 10, 2023
138
Therefore, the following is a valid PureScript code:
Dec 2, 2016
Dec 2, 2016
139
140
```haskell
141
add x y z = x +
142
y + z
143
```
144
Jul 10, 2023
Jul 10, 2023
145
But this is not a valid code:
Dec 2, 2016
Dec 2, 2016
146
147
```haskell
148
add x y z = x +
149
y + z
150
```
151
152
In the second case, the PureScript compiler will try to parse _two_ declarations, one for each line.
153
154
Generally, any declarations defined in the same block should be indented at the same level. For example, in PSCi, declarations in a let statement must be indented equally. This is valid:
155
156
```text
157
> :paste
Aug 12, 2017
Aug 12, 2017
158
… x = 1
159
… y = 2
Dec 2, 2016
Dec 2, 2016
160
… ^D
161
```
162
Jul 10, 2023
Jul 10, 2023
163
But this is not:
Dec 2, 2016
Dec 2, 2016
164
165
```text
166
> :paste
Aug 12, 2017
Aug 12, 2017
167
… x = 1
168
… y = 2
Dec 2, 2016
Dec 2, 2016
169
… ^D
170
```
171
Jul 17, 2023
Jul 17, 2023
172
Certain PureScript keywords introduce a new block of code, in which declarations must be further-indented:
Dec 2, 2016
Dec 2, 2016
173
174
```haskell
Jul 17, 2023
Jul 17, 2023
175
example x y z =
176
let
Dec 2, 2016
Dec 2, 2016
177
foo = x * y
178
bar = y * z
Jul 17, 2023
Jul 17, 2023
179
in
180
foo + bar
Dec 2, 2016
Dec 2, 2016
181
```
182
Jul 17, 2023
Jul 17, 2023
183
This doesn't compile:
Dec 2, 2016
Dec 2, 2016
184
Jul 17, 2023
Jul 17, 2023
185
```haskell
186
example x y z =
187
let
188
foo = x * y
189
bar = y * z
190
in
191
foo + bar
192
```
193
194
If you want to learn more (or encounter any problems), see the [Syntax](https://github.com/purescript/documentation/blob/master/language/Syntax.md#syntax) documentation.
Dec 2, 2016
Dec 2, 2016
195
196
## Defining Our Types
197
198
A good first step when tackling a new problem in PureScript is to write out type definitions for any values you will be working with. First, let's define a type for records in our address book:
199
200
```haskell
Aug 7, 2021
Aug 7, 2021
201
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:Entry}}
Dec 2, 2016
Dec 2, 2016
202
```
203
Jul 10, 2023
Jul 10, 2023
204
This defines a _type synonym_ called `Entry` – the type `Entry` is equivalent to the type on the right of the equals symbol: a record type with three fields – `firstName`, `lastName`, and `address`. The two name fields will have the type `String`, and the `address` field will have the type `Address`, defined as follows:
Dec 2, 2016
Dec 2, 2016
205
206
```haskell
Aug 7, 2021
Aug 7, 2021
207
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:Address}}
Dec 2, 2016
Dec 2, 2016
208
```
209
210
Note that records can contain other records.
211
Jul 10, 2023
Jul 10, 2023
212
Now let's define a third type synonym for our address book data structure, which will be represented simply as a linked list of entries:
Dec 2, 2016
Dec 2, 2016
213
214
```haskell
Aug 7, 2021
Aug 7, 2021
215
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:AddressBook}}
Dec 2, 2016
Dec 2, 2016
216
```
217
Jul 10, 2023
Jul 10, 2023
218
Note that `List Entry` differs from `Array Entry`, which represents an _array_ of entries.
Dec 2, 2016
Dec 2, 2016
219
220
## Type Constructors and Kinds
221
222
`List` is an example of a _type constructor_. Values do not have the type `List` directly, but rather `List a` for some type `a`. That is, `List` takes a _type argument_ `a` and _constructs_ a new type `List a`.
223
Jul 10, 2023
Jul 10, 2023
224
Note that just like function application, type constructors are applied to other types simply by juxtaposition: the type `List Entry` is, in fact, the type constructor `List` _applied_ to the type `Entry` – it represents a list of entries.
Dec 2, 2016
Dec 2, 2016
225
226
If we try to incorrectly define a value of type `List` (by using the type annotation operator `::`), we will see a new type of error:
227
228
```text
229
> import Data.List
230
> Nil :: List
Aug 12, 2017
Aug 12, 2017
231
In a type-annotated expression x :: t, the type t must have kind Type
Dec 2, 2016
Dec 2, 2016
232
```
233
234
This is a _kind error_. Just like values are distinguished by their _types_, types are distinguished by their _kinds_, and just like ill-typed values result in _type errors_, _ill-kinded_ types result in _kind errors_.
235
Aug 12, 2017
Aug 12, 2017
236
There is a special kind called `Type` which represents the kind of all types which have values, like `Number` and `String`.
Dec 2, 2016
Dec 2, 2016
237
Aug 12, 2017
Aug 12, 2017
238
There are also kinds for type constructors. For example, the kind `Type -> Type` represents a function from types to types, just like `List`. So the error here occurred because values are expected to have types with kind `Type`, but `List` has kind `Type -> Type`.
Dec 2, 2016
Dec 2, 2016
239
240
To find out the kind of a type, use the `:kind` command in PSCi. For example:
241
242
```text
243
> :kind Number
Aug 12, 2017
Aug 12, 2017
244
Type
Dec 2, 2016
Dec 2, 2016
245
246
> import Data.List
247
> :kind List
Aug 12, 2017
Aug 12, 2017
248
Type -> Type
Dec 2, 2016
Dec 2, 2016
249
250
> :kind List String
Aug 12, 2017
Aug 12, 2017
251
Type
Dec 2, 2016
Dec 2, 2016
252
```
253
254
PureScript's _kind system_ supports other interesting kinds, which we will see later in the book.
255
Jul 17, 2023
Jul 17, 2023
256
## Quantified Types
257
258
For illustration purposes, let's define a primitive function that takes any two arguments and returns the first one:
259
260
```text
261
> :paste
262
… constantlyFirst :: forall a b. a -> b -> a
263
… constantlyFirst = \a b -> a
264
… ^D
265
```
266
267
> Note that if you use `:type` to ask about the type of `constantlyFirst`, it will be more verbose:
268
>
269
> ```text
270
> : type constantlyFirst
271
> forall (a :: Type) (b :: Type). a -> b -> a
272
> ```
273
>
274
> The type signature contains additional kind information, which explicitly notes that `a` and `b` should be concrete types.
275
276
The keyword `forall` indicates that `constantlyFirst` has a _universally quantified type_. It means we can substitute any types for `a` and `b` – `constantlyFirst` will work with these types.
277
Aug 26, 2023
Aug 26, 2023
278
For example, we might choose the type `a` to be `Int` and `b` to be `String`. In that case, we can _specialize_ the type of `constantlyFirst` to
Jul 17, 2023
Jul 17, 2023
279
280
```text
281
Int -> String -> Int
282
```
283
284
We don't have to indicate in code that we want to specialize a quantified type – it happens automatically. For example, we can use `constantlyFirst` as if it had this type already:
285
286
```text
287
> constantlyFirst 3 "ignored"
288
289
3
290
```
291
292
While we can choose any types for `a` and `b`, the return type of `constantlyFirst` has to be the same as the type of the first argument (because both of them are "tied" to the same `a`):
293
294
```text
295
:type constantlyFirst true "ignored"
296
Boolean
297
298
:type constantlyFirst "keep" 3
299
String
300
```
301
Dec 2, 2016
Dec 2, 2016
302
## Displaying Address Book Entries
303
304
Let's write our first function, which will render an address book entry as a string. We start by giving the function a type. This is optional, but good practice, since it acts as a form of documentation. In fact, the PureScript compiler will give a warning if a top-level declaration does not contain a type annotation. A type declaration separates the name of a function from its type with the `::` symbol:
305
306
```haskell
Aug 7, 2021
Aug 7, 2021
307
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showEntry_signature}}
Dec 2, 2016
Dec 2, 2016
308
```
309
Jul 10, 2023
Jul 10, 2023
310
This type signature says that `showEntry` is a function that takes an `Entry` as an argument and returns a `String`. Here is the code for `showEntry`:
Dec 2, 2016
Dec 2, 2016
311
312
```haskell
Aug 7, 2021
Aug 7, 2021
313
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showEntry_implementation}}
Dec 2, 2016
Dec 2, 2016
314
```
315
316
This function concatenates the three fields of the `Entry` record into a single string, using the `showAddress` function to turn the record inside the `address` field into a `String`. `showAddress` is defined similarly:
317
318
```haskell
Aug 7, 2021
Aug 7, 2021
319
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showAddress}}
Dec 2, 2016
Dec 2, 2016
320
```
321
Sep 25, 2019
Sep 25, 2019
322
A function definition begins with the name of the function, followed by a list of argument names. The result of the function is specified after the equals sign. Fields are accessed with a dot, followed by the field name. In PureScript, string concatenation uses the diamond operator (`<>`), instead of the plus operator like in JavaScript.
Dec 2, 2016
Dec 2, 2016
323
324
## Test Early, Test Often
325
326
The PSCi interactive mode allows for rapid prototyping with immediate feedback, so let's use it to verify that our first few functions behave as expected.
327
328
First, build the code you've written:
329
330
```text
Oct 22, 2019
Oct 22, 2019
331
$ spago build
Dec 2, 2016
Dec 2, 2016
332
```
333
334
Next, load PSCi, and use the `import` command to import your new module:
335
336
```text
Oct 22, 2019
Oct 22, 2019
337
$ spago repl
Dec 2, 2016
Dec 2, 2016
338
339
> import Data.AddressBook
340
```
341
Mar 18, 2019
Mar 18, 2019
342
We can create an entry by using a record literal, which looks just like an anonymous object in JavaScript.
Dec 2, 2016
Dec 2, 2016
343
344
```text
Aug 12, 2017
Aug 12, 2017
345
> address = { street: "123 Fake St.", city: "Faketown", state: "CA" }
Dec 2, 2016
Dec 2, 2016
346
```
347
348
Now, try applying our function to the example:
349
350
```text
351
> showAddress address
352
353
"123 Fake St., Faketown, CA"
354
```
355
356
Let's also test `showEntry` by creating an address book entry record containing our example address:
357
358
```text
Aug 12, 2017
Aug 12, 2017
359
> entry = { firstName: "John", lastName: "Smith", address: address }
Dec 2, 2016
Dec 2, 2016
360
> showEntry entry
361
362
"Smith, John: 123 Fake St., Faketown, CA"
363
```
364
365
## Creating Address Books
366
Jul 10, 2023
Jul 10, 2023
367
Now let's write some utility functions for working with address books. We will need a value representing an empty address book: an empty list.
Dec 2, 2016
Dec 2, 2016
368
369
```haskell
Aug 7, 2021
Aug 7, 2021
370
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:emptyBook}}
Dec 2, 2016
Dec 2, 2016
371
```
372
373
We will also need a function for inserting a value into an existing address book. We will call this function `insertEntry`. Start by giving its type:
374
375
```haskell
Aug 7, 2021
Aug 7, 2021
376
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry_signature}}
Dec 2, 2016
Dec 2, 2016
377
```
378
Jul 10, 2023
Jul 10, 2023
379
This type signature says that `insertEntry` takes an `Entry` as its first argument, an `AddressBook` as a second argument, and returns a new `AddressBook`.
Dec 2, 2016
Dec 2, 2016
380
Jul 10, 2023
Jul 10, 2023
381
We don't modify the existing `AddressBook` directly. Instead, we return a new `AddressBook`, which contains the same data. As such, `AddressBook` is an example of an _immutable data structure_. This is an important idea in PureScript – mutation is a side-effect of code and inhibits our ability to reason effectively about its behavior, so we prefer pure functions and immutable data where possible.
Dec 2, 2016
Dec 2, 2016
382
383
To implement `insertEntry`, we can use the `Cons` function from `Data.List`. To see its type, open PSCi and use the `:type` command:
384
385
```text
Oct 22, 2019
Oct 22, 2019
386
$ spago repl
Dec 2, 2016
Dec 2, 2016
387
388
> import Data.List
389
> :type Cons
390
Jul 17, 2023
Jul 17, 2023
391
forall (a :: Type). a -> List a -> List a
Dec 2, 2016
Dec 2, 2016
392
```
393
Jul 10, 2023
Jul 10, 2023
394
This type signature says that `Cons` takes a value of some type `a`, takes a list of elements of type `a`, and returns a new list with entries of the same type. Let's specialize this with `a` as our `Entry` type:
Dec 2, 2016
Dec 2, 2016
395
396
```haskell
397
Entry -> List Entry -> List Entry
398
```
399
400
But `List Entry` is the same as `AddressBook`, so this is equivalent to
401
402
```haskell
403
Entry -> AddressBook -> AddressBook
404
```
405
Nov 21, 2021
Nov 21, 2021
406
In our case, we already have the appropriate inputs: an `Entry`, and an `AddressBook`, so can apply `Cons` and get a new `AddressBook`, which is exactly what we wanted!
Dec 2, 2016
Dec 2, 2016
407
408
Here is our implementation of `insertEntry`:
409
410
```haskell
411
insertEntry entry book = Cons entry book
412
```
413
Jul 10, 2023
Jul 10, 2023
414
This brings the two arguments `entry` and `book` into scope – on the left-hand side of the equals symbol – and then applies the `Cons` function to create the result.
Dec 2, 2016
Dec 2, 2016
415
416
## Curried Functions
417
Aug 19, 2023
Aug 19, 2023
418
Functions in PureScript take exactly one argument. While it looks like the `insertEntry` function takes two arguments, it is an example of a _curried function_. In PureScript, all functions are considered curried.
Dec 2, 2016
Dec 2, 2016
419
Aug 19, 2023
Aug 19, 2023
420
Currying means converting a function that takes multiple arguments into a function that takes them one at a time. When we call a function, we pass it one argument, and it returns another function that also takes one argument until all arguments are passed.
421
422
For example, when we pass `5` to `add`, we get another function, which takes an int, adds 5 to it, and returns the sum as a result:
423
424
```haskell
425
add :: Int -> Int -> Int
426
add x y = x + y
427
428
addFive :: Int -> Int
429
addFive = add 5
430
```
431
432
`addFive` is the result of _partial application_, which means we pass less than the total number of arguments to a function that takes multiple arguments. Let's give it a try:
433
434
> Note that you must define the `add` function if you haven't already:
435
>
436
> ```text
437
> > import Prelude
438
> > :paste
439
>… add :: Int -> Int -> Int
440
>… add x y = x + y
441
>… ^D
442
> ```
443
444
```text
445
> :paste
446
… addFive :: Int -> Int
447
… addFive = add 5
448
… ^D
449
450
> addFive 1
451
6
452
453
> add 5 1
454
6
455
```
456
457
To better understand currying and partial application, try making a few other functions, for example, out of `add`. And when you're done, let's return to the `insertEntry`.
458
459
```haskell
460
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry_signature}}
461
```
462
463
The `->` operator (in the type signature) associates to the right, which means that the compiler parses the type as
Dec 2, 2016
Dec 2, 2016
464
465
```haskell
466
Entry -> (AddressBook -> AddressBook)
467
```
468
Aug 19, 2023
Aug 19, 2023
469
`insertEntry` takes a single argument, an `Entry`, and returns a new function, which in turn takes a single `AddressBook` argument and returns a new `AddressBook`.
Dec 2, 2016
Dec 2, 2016
470
Jul 10, 2023
Jul 10, 2023
471
This means we can _partially apply_ `insertEntry` by specifying only its first argument, for example. In PSCi, we can see the result type:
Dec 2, 2016
Dec 2, 2016
472
473
```text
474
> :type insertEntry entry
475
476
AddressBook -> AddressBook
477
```
478
479
As expected, the return type was a function. We can apply the resulting function to a second argument:
480
481
```text
482
> :type (insertEntry entry) emptyBook
483
AddressBook
484
```
485
Jul 10, 2023
Jul 10, 2023
486
Note though, that the parentheses here are unnecessary – the following is equivalent:
Dec 2, 2016
Dec 2, 2016
487
488
```text
Mar 27, 2020
Mar 27, 2020
489
> :type insertEntry entry emptyBook
Dec 2, 2016
Dec 2, 2016
490
AddressBook
491
```
492
Jul 10, 2023
Jul 10, 2023
493
This is because function application associates to the left, which explains why we can specify function arguments one after the other, separated by whitespace.
Dec 2, 2016
Dec 2, 2016
494
Jul 10, 2023
Jul 10, 2023
495
The `->` operator in function types is a _type constructor_ for functions. It takes two type arguments: the function's argument type and the return type – the left and right operands, respectively.
Jun 6, 2021
Jun 6, 2021
496
497
Note that in the rest of the book, I will talk about things like "functions of two arguments". However, it is to be understood that this means a curried function, taking a first argument and returning a function that takes the second.
Dec 2, 2016
Dec 2, 2016
498
499
Now consider the definition of `insertEntry`:
500
501
```haskell
502
insertEntry :: Entry -> AddressBook -> AddressBook
503
insertEntry entry book = Cons entry book
504
```
505
Jul 10, 2023
Jul 10, 2023
506
If we explicitly parenthesize the right-hand side, we get `(Cons entry) book`. That is, `insertEntry entry` is a function whose argument is just passed along to the `(Cons entry)` function. But if two functions have the same result for every input, then they are the same! So we can remove the argument `book` from both sides:
Dec 2, 2016
Dec 2, 2016
507
508
```haskell
509
insertEntry :: Entry -> AddressBook -> AddressBook
510
insertEntry entry = Cons entry
511
```
512
513
But now, by the same argument, we can remove `entry` from both sides:
514
515
```haskell
Aug 7, 2021
Aug 7, 2021
516
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry}}
Dec 2, 2016
Dec 2, 2016
517
```
518
Jul 10, 2023
Jul 10, 2023
519
This process, called _eta conversion_, can be used (along with other techniques) to rewrite functions in _point-free form_, which means functions defined without reference to their arguments.
Dec 2, 2016
Dec 2, 2016
520
Jul 10, 2023
Jul 10, 2023
521
In the case of `insertEntry`, _eta conversion_ has resulted in a very clear definition of our function – "`insertEntry` is just cons on lists". However, it is arguable whether the point-free form is better in general.
Dec 2, 2016
Dec 2, 2016
522
Jan 5, 2021
Jan 5, 2021
523
## Property Accessors
524
525
One common pattern is to use a function to access individual fields (or "properties") of a record. An inline function to extract an `Address` from an `Entry` could be written as:
526
527
```haskell
528
\entry -> entry.address
529
```
530
Apr 1, 2021
Apr 1, 2021
531
PureScript also allows [_property accessor_](https://github.com/purescript/documentation/blob/master/language/Syntax.md#property-accessors) shorthand, where an underscore acts as the anonymous function argument, so the inline function above is equivalent to:
Jan 5, 2021
Jan 5, 2021
532
533
```haskell
534
_.address
535
```
536
537
This works with any number of levels or properties, so a function to extract the city associated with an `Entry` could be written as:
538
539
```haskell
540
_.address.city
541
```
542
543
For example:
544
545
```text
546
> address = { street: "123 Fake St.", city: "Faketown", state: "CA" }
547
> entry = { firstName: "John", lastName: "Smith", address: address }
548
> _.lastName entry
549
"Smith"
550
551
> _.address.city entry
552
"Faketown"
553
```
554
Dec 2, 2016
Dec 2, 2016
555
## Querying the Address Book
556
Jul 10, 2023
Jul 10, 2023
557
The last function we need to implement for our minimal address book application will look up a person by name and return the correct `Entry`. This will be a nice application of building programs by composing small functions – a key idea from functional programming.
Dec 2, 2016
Dec 2, 2016
558
Jul 10, 2023
Jul 10, 2023
559
We can filter the address book, keeping only those entries with the correct first and last names. Then we can return the head (i.e., first) element of the resulting list.
Dec 2, 2016
Dec 2, 2016
560
Jul 10, 2023
Jul 10, 2023
561
With this high-level specification of our approach, we can calculate the type of our function. First, open PSCi, and find the types of the `filter` and `head` functions:
Dec 2, 2016
Dec 2, 2016
562
563
```text
Oct 22, 2019
Oct 22, 2019
564
$ spago repl
Dec 2, 2016
Dec 2, 2016
565
566
> import Data.List
567
> :type filter
568
Jul 17, 2023
Jul 17, 2023
569
forall (a :: Type). (a -> Boolean) -> List a -> List a
Dec 2, 2016
Dec 2, 2016
570
571
> :type head
572
Jul 17, 2023
Jul 17, 2023
573
forall (a :: Type). List a -> Maybe a
Dec 2, 2016
Dec 2, 2016
574
```
575
576
Let's pick apart these two types to understand their meaning.
577
Jul 10, 2023
Jul 10, 2023
578
`filter` is a curried function of two arguments. Its first argument is a function, which takes an element of the list and returns a `Boolean` value. Its second argument is a list of elements, and the return value is another list.
Dec 2, 2016
Dec 2, 2016
579
Jul 10, 2023
Jul 10, 2023
580
`head` takes a list as its argument and returns a type we haven't seen before: `Maybe a`. `Maybe a` represents an optional value of type `a`, and provides a type-safe alternative to using `null` to indicate a missing value in languages like JavaScript. We will see it again in more detail in later chapters.
Dec 2, 2016
Dec 2, 2016
581
582
The universally quantified types of `filter` and `head` can be _specialized_ by the PureScript compiler, to the following types:
583
584
```haskell
585
filter :: (Entry -> Boolean) -> AddressBook -> AddressBook
586
587
head :: AddressBook -> Maybe Entry
588
```
589
Jul 10, 2023
Jul 10, 2023
590
We know that we will need to pass the first and last names that we want to search for as arguments to our function.
Dec 2, 2016
Dec 2, 2016
591
592
We also know that we will need a function to pass to `filter`. Let's call this function `filterEntry`. `filterEntry` will have type `Entry -> Boolean`. The application `filter filterEntry` will then have type `AddressBook -> AddressBook`. If we pass the result of this function to the `head` function, we get our result of type `Maybe Entry`.
593
594
Putting these facts together, a reasonable type signature for our function, which we will call `findEntry`, is:
595
596
```haskell
Aug 7, 2021
Aug 7, 2021
597
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:findEntry_signature}}
Dec 2, 2016
Dec 2, 2016
598
```
599
Jul 10, 2023
Jul 10, 2023
600
This type signature says that `findEntry` takes two strings: the first and last names, takes an `AddressBook`, and returns an optional `Entry`. The optional result will contain a value only if the name is found in the address book.
Dec 2, 2016
Dec 2, 2016
601
602
And here is the definition of `findEntry`:
603
604
```haskell
Jan 5, 2021
Jan 5, 2021
605
findEntry firstName lastName book = head (filter filterEntry book)
Dec 2, 2016
Dec 2, 2016
606
where
607
filterEntry :: Entry -> Boolean
608
filterEntry entry = entry.firstName == firstName && entry.lastName == lastName
609
```
610
611
Let's go over this code step by step.
612
Jul 27, 2022
Jul 27, 2022
613
`findEntry` brings three names into scope: `firstName` and `lastName`, both representing strings, and `book`, an `AddressBook`.
Dec 2, 2016
Dec 2, 2016
614
Jul 10, 2023
Jul 10, 2023
615
The right-hand side of the definition combines the `filter` and `head` functions: first, the list of entries is filtered, and the `head` function is applied to the result.
Dec 2, 2016
Dec 2, 2016
616
617
The predicate function `filterEntry` is defined as an auxiliary declaration inside a `where` clause. This way, the `filterEntry` function is available inside the definition of our function, but not outside it. Also, it can depend on the arguments to the enclosing function, which is essential here because `filterEntry` uses the `firstName` and `lastName` arguments to filter the specified `Entry`.
618
Jul 10, 2023
Jul 10, 2023
619
Note that, just like for top-level declarations, it was unnecessary to specify a type signature for `filterEntry`. However, doing so is recommended as a form of documentation.
Dec 2, 2016
Dec 2, 2016
620
621
## Infix Function Application
622
Jul 10, 2023
Jul 10, 2023
623
Most functions discussed so far used _prefix_ function application, where the function name was put _before_ the arguments. For example, when using the `insertEntry` function to add an `Entry` (`john`) to an empty `AddressBook`, we might write:
Jan 5, 2021
Jan 5, 2021
624
625
```haskell
626
> book1 = insertEntry john emptyBook
627
```
628
Jul 10, 2023
Jul 10, 2023
629
However, this chapter has also included examples of _infix_ [binary operators](https://github.com/purescript/documentation/blob/master/language/Syntax.md#binary-operators), such as the `==` operator in the definition of `filterEntry`, where the operator is put _between_ the two arguments. These infix operators are defined in the PureScript source as infix aliases for their underlying _prefix_ implementations. For example, `==` is defined as an infix alias for the prefix `eq` function with the line:
Jan 5, 2021
Jan 5, 2021
630
631
```haskell
632
infix 4 eq as ==
633
```
634
Jul 10, 2023
Jul 10, 2023
635
Therefore `entry.firstName == firstName` in `filterEntry` could be replaced with the `eq entry.firstName firstName`. We'll cover a few more examples of defining infix operators later in this section.
Jan 5, 2021
Jan 5, 2021
636
Jul 10, 2023
Jul 10, 2023
637
In some situations, putting a prefix function in an infix position as an operator leads to more readable code. One example is the `mod` function:
Dec 2, 2016
Dec 2, 2016
638
Jan 5, 2021
Jan 5, 2021
639
```text
640
> mod 8 3
641
2
642
```
643
Jul 10, 2023
Jul 10, 2023
644
The above usage works fine but is awkward to read. A more familiar phrasing is "eight mod three", which you can achieve by wrapping a prefix function in backticks (\`):
Jan 5, 2021
Jan 5, 2021
645
646
```text
647
> 8 `mod` 3
648
2
649
```
Dec 2, 2016
Dec 2, 2016
650
Jan 5, 2021
Jan 5, 2021
651
In the same way, wrapping `insertEntry` in backticks turns it into an infix operator, such that `book1` and `book2` below are equivalent:
652
653
```haskell
654
book1 = insertEntry john emptyBook
655
book2 = john `insertEntry` emptyBook
656
```
657
658
We can make an `AddressBook` with multiple entries by using multiple applications of `insertEntry` as a prefix function (`book3`) or as an infix operator (`book4`) as shown below:
659
660
```haskell
661
book3 = insertEntry john (insertEntry peggy (insertEntry ned emptyBook))
662
book4 = john `insertEntry` (peggy `insertEntry` (ned `insertEntry` emptyBook))
663
```
664
665
We can also define an infix operator alias (or synonym) for `insertEntry.` We'll arbitrarily choose `++` for this operator, give it a [precedence](https://github.com/purescript/documentation/blob/master/language/Syntax.md#precedence) of `5`, and make it right [associative](https://github.com/purescript/documentation/blob/master/language/Syntax.md#associativity) using `infixr`:
666
667
```haskell
668
infixr 5 insertEntry as ++
669
```
670
671
This new operator lets us rewrite the above `book4` example as:
672
673
```haskell
674
book5 = john ++ (peggy ++ (ned ++ emptyBook))
675
```
676
Jul 10, 2023
Jul 10, 2023
677
The right associativity of our new `++` operator lets us get rid of the parentheses without changing the meaning:
Jan 5, 2021
Jan 5, 2021
678
679
```haskell
680
book6 = john ++ peggy ++ ned ++ emptyBook
681
```
682
683
Another common technique for eliminating parens is to use `apply`'s infix operator `$`, along with your standard prefix functions.
684
685
For example, the earlier `book3` example could be rewritten as:
Jan 5, 2023
Jan 5, 2023
686
Jan 5, 2021
Jan 5, 2021
687
```haskell
688
book7 = insertEntry john $ insertEntry peggy $ insertEntry ned emptyBook
689
```
690
691
Substituting `$` for parens is usually easier to type and (arguably) easier to read. A mnemonic to remember the meaning of this symbol is to think of the dollar sign as being drawn from two parens that are also being crossed-out, suggesting the parens are now unnecessary.
692
Jul 10, 2023
Jul 10, 2023
693
Note that `$` isn't a special syntax hardcoded into the language. It's simply the infix operator for a regular function called `apply`, which is defined in `Data.Function` as follows:
Dec 2, 2016
Dec 2, 2016
694
695
```haskell
696
apply :: forall a b. (a -> b) -> a -> b
697
apply f x = f x
698
699
infixr 0 apply as $
700
```
701
Jan 5, 2021
Jan 5, 2021
702
The `apply` function takes another function (of type `(a -> b)`) as its first argument and a value (of type `a`) as its second argument, then calls that function with that value. If it seems like this function doesn't contribute anything meaningful, you are absolutely correct! Your program is logically identical without it (see [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency)). The syntactic utility of this function comes from the special properties assigned to its infix operator. `$` is a right-associative (`infixr`), low precedence (`0`) operator, which lets us remove sets of parentheses for deeply-nested applications.
Dec 2, 2016
Dec 2, 2016
703
Jan 5, 2021
Jan 5, 2021
704
Another parens-busting opportunity for the `$` operator is in our earlier `findEntry` function:
Jan 5, 2023
Jan 5, 2023
705
Jan 5, 2021
Jan 5, 2021
706
```haskell
707
findEntry firstName lastName book = head $ filter filterEntry book
708
```
Jan 5, 2023
Jan 5, 2023
709
Jan 5, 2021
Jan 5, 2021
710
We'll see an even more elegant way to rewrite this line with "function composition" in the next section.
Dec 2, 2016
Dec 2, 2016
711
Jan 5, 2021
Jan 5, 2021
712
If you'd like to use a concise infix operator alias as a prefix function, you can surround it in parentheses:
Dec 2, 2016
Dec 2, 2016
713
Jan 5, 2021
Jan 5, 2021
714
```text
715
> 8 + 3
716
11
717
718
> (+) 8 3
719
11
720
```
721
722
Alternatively, operators can be partially applied by surrounding the expression with parentheses and using `_` as an operand in an [operator section](https://github.com/purescript/documentation/blob/master/language/Syntax.md#operator-sections). You can think of this as a more convenient way to create simple anonymous functions (although in the below example, we're then binding that anonymous function to a name, so it's not so anonymous anymore):
723
724
```text
725
> add3 = (3 + _)
726
> add3 2
727
5
Dec 2, 2016
Dec 2, 2016
728
```
729
Jan 5, 2021
Jan 5, 2021
730
To summarize, the following are equivalent definitions of a function that adds `5` to its argument:
Dec 2, 2016
Dec 2, 2016
731
732
```haskell
Jan 5, 2021
Jan 5, 2021
733
add5 x = 5 + x
734
add5 x = add 5 x
735
add5 x = (+) 5 x
736
add5 x = 5 `add` x
737
add5 = add 5
738
add5 = \x -> 5 + x
739
add5 = (5 + _)
740
add5 x = 5 `(+)` x -- Yo Dawg, I herd you like infix, so we put infix in your infix!
Dec 2, 2016
Dec 2, 2016
741
```
742
743
## Function Composition
744
745
Just like we were able to simplify the `insertEntry` function by using eta conversion, we can simplify the definition of `findEntry` by reasoning about its arguments.
746
747
Note that the `book` argument is passed to the `filter filterEntry` function, and the result of this application is passed to `head`. In other words, `book` is passed to the _composition_ of the functions `filter filterEntry` and `head`.
748
749
In PureScript, the function composition operators are `<<<` and `>>>`. The first is "backwards composition", and the second is "forwards composition".
750
751
We can rewrite the right-hand side of `findEntry` using either operator. Using backwards-composition, the right-hand side would be
752
Jan 5, 2023
Jan 5, 2023
753
```haskell
Dec 2, 2016
Dec 2, 2016
754
(head <<< filter filterEntry) book
755
```
756
757
In this form, we can apply the eta conversion trick from earlier, to arrive at the final form of `findEntry`:
758
759
```haskell
Aug 7, 2021
Aug 7, 2021
760
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:findEntry_implementation}}
Dec 2, 2016
Dec 2, 2016
761
...
762
```
763
764
An equally valid right-hand side would be:
765
766
```haskell
767
filter filterEntry >>> head
768
```
769
770
Either way, this gives a clear definition of the `findEntry` function: "`findEntry` is the composition of a filtering function and the `head` function".
771
Jul 10, 2023
Jul 10, 2023
772
I will let you decide which definition is easier to understand, but it is often useful to think of functions as building blocks in this way: each function executes a single task, and solutions are assembled using function composition.
Dec 2, 2016
Dec 2, 2016
773
Sep 8, 2019
Sep 8, 2019
774
## Exercises
Apr 1, 2019
Apr 1, 2019
775
Jun 12, 2020
Jun 12, 2020
776
1. (Easy) Test your understanding of the `findEntry` function by writing down the types of each of its major subexpressions. For example, the type of the `head` function as used is specialized to `AddressBook -> Maybe Entry`. _Note_: There is no test for this exercise.
777
1. (Medium) Write a function `findEntryByStreet :: String -> AddressBook -> Maybe Entry` which looks up an `Entry` given a street address. _Hint_ reusing the existing code in `findEntry`. Test your function in PSCi and by running `spago test`.
Jan 5, 2021
Jan 5, 2021
778
1. (Medium) Rewrite `findEntryByStreet` to replace `filterEntry` with the composition (using `<<<` or `>>>`) of: a property accessor (using the `_.` notation); and a function that tests whether its given string argument is equal to the given street address.
Jul 10, 2023
Jul 10, 2023
779
1. (Medium) Write a function `isInBook` that tests whether a name appears in a `AddressBook`, returning a Boolean value. _Hint_: Use PSCi to find the type of the `Data.List.null` function, which tests whether a list is empty or not.
780
1. (Difficult) Write a function `removeDuplicates` which removes "duplicate" address book entries. We'll consider entries duplicated if they share the same first and last names, while ignoring `address` fields. _Hint_: Use PSCi to find the type of the `Data.List.nubByEq` function, which removes duplicate elements from a list based on an equality predicate. Note that the first element in each set of duplicates (closest to the list head) is the one that is kept.
Dec 2, 2016
Dec 2, 2016
781
782
## Conclusion
783
Jul 10, 2023
Jul 10, 2023
784
In this chapter, we covered several new functional programming concepts and learned how to:
Dec 2, 2016
Dec 2, 2016
785
Jul 10, 2023
Jul 10, 2023
786
- Use the interactive mode PSCi to experiment with functions and test ideas.
787
- Use types as both a correctness tool and an implementation tool.
788
- Use curried functions to represent functions of multiple arguments.
789
- Create programs from smaller components by composition.
790
- Structure code neatly using `where` expressions.
791
- Avoid null values by using the `Maybe` type.
792
- Use techniques like eta conversion and function composition to refactor code into a clear specification.
Dec 2, 2016
Dec 2, 2016
793
794
In the following chapters, we'll build on these ideas.