Skip to content

Commit d8d83c6

Browse files
ntwilsonmilesfrain
andauthored
Chapter3 anchors (#372)
* Chapter 2 - Add anchors for code snippets * move a comment from the code to the mdbook text, since it shouldn't exist in the final version that learners work with * Anchors for all final code blocks for chapter 3 * add a caution comment to maintainers about an implementation that is copied instead of anchored * another maintainer caution code comment * Rewording a bit on infix functions for clarity Co-authored-by: milesfrain <[email protected]> Co-authored-by: milesfrain <[email protected]>
1 parent 218530a commit d8d83c6

File tree

2 files changed

+46
-38
lines changed

2 files changed

+46
-38
lines changed
+33-3
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,70 @@
1+
-- ANCHOR: imports
12
module Data.AddressBook where
23

34
import Prelude
45

56
import Control.Plus (empty)
67
import Data.List (List(..), filter, head)
78
import Data.Maybe (Maybe)
9+
-- ANCHOR_END: imports
810

11+
-- ANCHOR: Address
912
type Address =
1013
{ street :: String
1114
, city :: String
1215
, state :: String
1316
}
17+
-- ANCHOR_END: Address
1418

19+
-- ANCHOR: Entry
1520
type Entry =
1621
{ firstName :: String
1722
, lastName :: String
1823
, address :: Address
1924
}
25+
-- ANCHOR_END: Entry
2026

27+
-- ANCHOR: AddressBook
2128
type AddressBook = List Entry
29+
-- ANCHOR_END: AddressBook
2230

31+
-- ANCHOR: showAddress
2332
showAddress :: Address -> String
24-
showAddress addr = addr.street <> ", " <> addr.city <> ", " <> addr.state
33+
showAddress addr = addr.street <> ", " <>
34+
addr.city <> ", " <>
35+
addr.state
36+
-- ANCHOR_END: showAddress
2537

38+
-- ANCHOR: showEntry_signature
2639
showEntry :: Entry -> String
27-
showEntry entry = entry.lastName <> ", " <> entry.firstName <> ": " <> showAddress entry.address
40+
-- ANCHOR_END: showEntry_signature
41+
-- ANCHOR: showEntry_implementation
42+
showEntry entry = entry.lastName <> ", " <>
43+
entry.firstName <> ": " <>
44+
showAddress entry.address
45+
-- ANCHOR_END: showEntry_implementation
2846

47+
-- ANCHOR: emptyBook
2948
emptyBook :: AddressBook
3049
emptyBook = empty
50+
-- ANCHOR_END: emptyBook
3151

52+
-- Note to reader: Delete this line. MAINTAINER CAUTION: There is an alternate copy of this implementation in the book source
53+
-- ANCHOR: insertEntry
54+
-- ANCHOR: insertEntry_signature
3255
insertEntry :: Entry -> AddressBook -> AddressBook
33-
insertEntry = Cons
56+
-- ANCHOR_END: insertEntry_signature
57+
insertEntry = Cons
58+
-- ANCHOR_END: insertEntry
3459

60+
-- Note to reader: Delete this line. MAINTAINER CAUTION: There is an alternate copy of this implementation in the book source
61+
-- ANCHOR: findEntry_signature
3562
findEntry :: String -> String -> AddressBook -> Maybe Entry
63+
-- ANCHOR_END: findEntry_signature
64+
-- ANCHOR: findEntry_implementation
3665
findEntry firstName lastName = head <<< filter filterEntry
3766
where
67+
-- ANCHOR_END: findEntry_implementation
3868
filterEntry :: Entry -> Boolean
3969
filterEntry entry = entry.firstName == firstName && entry.lastName == lastName
4070

text/chapter3.md

+13-35
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ The front-end of our application will be the interactive mode PSCi, but it would
1313
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:
1414

1515
```haskell
16-
module Data.AddressBook where
17-
18-
import Prelude
19-
20-
import Control.Plus (empty)
21-
import Data.List (List(..), filter, head)
22-
import Data.Maybe (Maybe)
16+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:imports}}
2317
```
2418

2519
Here, we import several modules:
@@ -233,29 +227,21 @@ The only exception to this rule is the `where` keyword in the initial `module` d
233227
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:
234228

235229
```haskell
236-
type Entry =
237-
{ firstName :: String
238-
, lastName :: String
239-
, address :: Address
240-
}
230+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:Entry}}
241231
```
242232

243233
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 type `String`, and the `address` field will have type `Address`, defined as follows:
244234

245235
```haskell
246-
type Address =
247-
{ street :: String
248-
, city :: String
249-
, state :: String
250-
}
236+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:Address}}
251237
```
252238

253239
Note that records can contain other records.
254240

255241
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:
256242

257243
```haskell
258-
type AddressBook = List Entry
244+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:AddressBook}}
259245
```
260246

261247
Note that `List Entry` is not the same as `Array Entry`, which represents an _array_ of entries.
@@ -301,24 +287,19 @@ PureScript's _kind system_ supports other interesting kinds, which we will see l
301287
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:
302288

303289
```haskell
304-
showEntry :: Entry -> String
290+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showEntry_signature}}
305291
```
306292

307293
This type signature says that `showEntry` is a function, which takes an `Entry` as an argument and returns a `String`. Here is the code for `showEntry`:
308294

309295
```haskell
310-
showEntry entry = entry.lastName <> ", " <>
311-
entry.firstName <> ": " <>
312-
showAddress entry.address
296+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showEntry_implementation}}
313297
```
314298

315299
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:
316300

317301
```haskell
318-
showAddress :: Address -> String
319-
showAddress addr = addr.street <> ", " <>
320-
addr.city <> ", " <>
321-
addr.state
302+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:showAddress}}
322303
```
323304

324305
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.
@@ -369,14 +350,13 @@ Let's also test `showEntry` by creating an address book entry record containing
369350
Now let's write some utility functions for working with address books. We will need a value which represents an empty address book: an empty list.
370351

371352
```haskell
372-
emptyBook :: AddressBook
373-
emptyBook = empty
353+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:emptyBook}}
374354
```
375355

376356
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:
377357

378358
```haskell
379-
insertEntry :: Entry -> AddressBook -> AddressBook
359+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry_signature}}
380360
```
381361

382362
This type signature says that `insertEntry` takes an `Entry` as its first argument, and an `AddressBook` as a second argument, and returns a new `AddressBook`.
@@ -473,8 +453,7 @@ insertEntry entry = Cons entry
473453
But now, by the same argument, we can remove `entry` from both sides:
474454

475455
```haskell
476-
insertEntry :: Entry -> AddressBook -> AddressBook
477-
insertEntry = Cons
456+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:insertEntry}}
478457
```
479458

480459
This process is called _eta conversion_, and can be used (along with some other techniques) to rewrite functions in _point-free form_, which means functions defined without reference to their arguments.
@@ -555,7 +534,7 @@ We also know that we will need a function to pass to `filter`. Let's call this f
555534
Putting these facts together, a reasonable type signature for our function, which we will call `findEntry`, is:
556535

557536
```haskell
558-
findEntry :: String -> String -> AddressBook -> Maybe Entry
537+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:findEntry_signature}}
559538
```
560539

561540
This type signature says that `findEntry` takes two strings, the first and last names, and a `AddressBook`, and returns an optional `Entry`. The optional result will contain a value only if the name is found in the address book.
@@ -602,7 +581,7 @@ There are situations where putting a prefix function in an infix position as an
602581
2
603582
```
604583

605-
This is fine, but doesn't line up with common usage (in conversation, one might say "eight mod three"). Wrapping a prefix function in backticks (\`) lets you use that it in infix position as an operator, e.g.,
584+
The above usage works fine, but is awkward to read. A more familiar phasing is "eight mod three", which you can achieve by wrapping a prefix function in backticks (\`):
606585

607586
```text
608587
> 8 `mod` 3
@@ -715,8 +694,7 @@ We can rewrite the right-hand side of `findEntry` using either operator. Using b
715694
In this form, we can apply the eta conversion trick from earlier, to arrive at the final form of `findEntry`:
716695

717696
```haskell
718-
findEntry firstName lastName = head <<< filter filterEntry
719-
where
697+
{{#include ../exercises/chapter3/src/Data/AddressBook.purs:findEntry_implementation}}
720698
...
721699
```
722700

0 commit comments

Comments
 (0)