You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/chapter3.md
+132-11
Original file line number
Diff line number
Diff line change
@@ -479,6 +479,38 @@ This process is called _eta conversion_, and can be used (along with some other
479
479
480
480
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 point-free form is better in general.
481
481
482
+
## Property Accessors
483
+
484
+
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:
485
+
486
+
```haskell
487
+
\entry -> entry.address
488
+
```
489
+
490
+
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 fuction argument, so the inline function above is equivalent to:
491
+
492
+
```haskell
493
+
_.address
494
+
```
495
+
496
+
This works with any number of levels or properties, so a function to extract the city associated with an `Entry` could be written as:
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.
@@ -529,7 +561,7 @@ This type signature says that `findEntry` takes two strings, the first and last
529
561
And here is the definition of`findEntry`:
530
562
531
563
```haskell
532
-
findEntry firstName lastName book =head$filter filterEntry book
564
+
findEntry firstName lastName book =head(filter filterEntry book)
@@ -547,11 +579,76 @@ Note that, just like for top-level declarations, it was not necessary to specify
547
579
548
580
## Infix Function Application
549
581
550
-
In the code for `findEntry` above, we used a different form of function application: the `head` function was applied to the expression `filter filterEntry book` by using the infix `$` symbol.
582
+
Most of the 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:
583
+
584
+
```haskell
585
+
> book1 = insertEntry john emptyBook
586
+
```
587
+
588
+
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 actually 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:
589
+
590
+
```haskell
591
+
infix4 eq as ==
592
+
```
593
+
594
+
and 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.
595
+
596
+
There are situations where putting a prefix function in an infix position as an operator leads to more readable code. One example is the `mod` function:
551
597
552
-
This is equivalent to the usual application `head (filter filterEntry book)`
598
+
```text
599
+
> mod 8 3
600
+
2
601
+
```
602
+
603
+
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.,
604
+
605
+
```text
606
+
> 8 `mod` 3
607
+
2
608
+
```
553
609
554
-
`($)` is just an alias for a regular function called `apply`, which is defined in the Prelude. It is defined as follows:
610
+
In the same way, wrapping `insertEntry` in backticks turns it into an infix operator, such that `book1` and `book2` below are equivalent:
611
+
612
+
```haskell
613
+
book1 = insertEntry john emptyBook
614
+
book2 = john `insertEntry` emptyBook
615
+
```
616
+
617
+
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:
618
+
619
+
```haskell
620
+
book3 = insertEntry john (insertEntry peggy (insertEntry ned emptyBook))
621
+
book4 = john `insertEntry` (peggy `insertEntry` (ned `insertEntry` emptyBook))
622
+
```
623
+
624
+
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`:
625
+
626
+
```haskell
627
+
infixr5 insertEntry as ++
628
+
```
629
+
630
+
This new operator lets us rewrite the above `book4` example as:
631
+
632
+
```haskell
633
+
book5 = john ++ (peggy ++ (ned ++ emptyBook))
634
+
```
635
+
636
+
and the right associativity of our new `++` operator lets us get rid of the parentheses without changing the meaning:
637
+
638
+
```haskell
639
+
book6 = john ++ peggy ++ ned ++ emptyBook
640
+
```
641
+
642
+
Another common technique for eliminating parens is to use `apply`'s infix operator `$`, along with your standard prefix functions.
643
+
644
+
For example, the earlier `book3` example could be rewritten as:
645
+
```haskell
646
+
book7 = insertEntry john $ insertEntry peggy $ insertEntry ned emptyBook
647
+
```
648
+
649
+
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.
650
+
651
+
Note that `$` isn't special syntax that's hardcoded into the language. It's simply the infix operator for a regular function called `apply`, which is defined in the Prelude as follows:
555
652
556
653
```haskell
557
654
apply::forallab. (a->b) ->a->b
@@ -560,20 +657,43 @@ apply f x = f x
560
657
infixr0 apply as $
561
658
```
562
659
563
-
So`apply` takes a function and a value and applies the function to the value. The `infixr` keyword is used to define `($)` as an alias for `apply`.
660
+
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.
564
661
565
-
But why would we want to use `$` instead of regular function application? The reason is that `$` is a right-associative, low precedence operator. This means that `$` allows us to remove sets of parentheses for deeply-nested applications.
662
+
Another parens-busting opportunity for the `$` operator is in our earlier `findEntry` function:
663
+
```haskell
664
+
findEntry firstName lastName book =head$filter filterEntry book
665
+
```
666
+
We'll see an even more elegant way to rewrite this line with "function composition" in the next section.
566
667
567
-
For example, the following nested function application, which finds the street in the address of an employee's boss:
668
+
If you'd like to use a concise infix operator alias as a prefix function, you can surround it in parentheses:
568
669
569
-
```haskell
570
-
street (address (boss employee))
670
+
```text
671
+
> 8 + 3
672
+
11
673
+
674
+
> (+) 8 3
675
+
11
676
+
```
677
+
678
+
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):
679
+
680
+
```text
681
+
> add3 = (3 + _)
682
+
> add3 2
683
+
5
571
684
```
572
685
573
-
becomes (arguably) easier to read when expressed using `$`:
686
+
To summarize, the following are equivalent definitions of a function that adds `5` to its argument:
574
687
575
688
```haskell
576
-
street $ address $ boss employee
689
+
add5 x =5+ x
690
+
add5 x = add 5 x
691
+
add5 x =(+)5 x
692
+
add5 x =5`add` x
693
+
add5 = add 5
694
+
add5 =\x ->5+ x
695
+
add5 = (5+ _)
696
+
add5 x =5 `(+)` x -- Yo Dawg, I herd you like infix, so we put infix in your infix!
577
697
```
578
698
579
699
## Function Composition
@@ -612,6 +732,7 @@ I will let you make your own decision which definition is easier to understand,
612
732
613
733
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.
614
734
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`.
735
+
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.
615
736
1. (Medium) Write a function `isInBook` which 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.
616
737
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.nubBy` 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 list head) is the one that is kept.
0 commit comments