-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Describe prefix/infix functions and record field accessors in Chapter 3 #285
Changes from 6 commits
7326fab
580ba98
55c276b
c8e2105
0c5a2fe
fbec125
f50148e
6f498f2
cee541b
e17346a
abc0718
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | |
|
||
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. | ||
|
||
## Property Accessors | ||
|
||
One common pattern is to use a function to access an individual fields (or "properties") of a record. An inline function to extract an `Address` from an `Entry` could be written as: | ||
|
||
```haskell | ||
\entry -> entry.address | ||
``` | ||
|
||
PureScript provides an equivalent [_property accessor_](https://github.com/purescript/documentation/blob/master/language/Syntax.md#property-accessors) shorthand, where an underscore is followed by a field name, so the inline function above is equivalent to: | ||
|
||
```haskell | ||
_.address | ||
``` | ||
|
||
This works with any number of levels or properties, so a function to extract the city associated with an `Entry` could be written as: | ||
|
||
```haskell | ||
_.address.city | ||
``` | ||
|
||
For example: | ||
|
||
```text | ||
> address = { street: "123 Fake St.", city: "Faketown", state: "CA" } | ||
> entry = { firstName: "John", lastName: "Smith", address: address } | ||
> _.lastName entry | ||
"Smith" | ||
|
||
> _.address.city entry | ||
"Faketown" | ||
``` | ||
|
||
## Querying the Address Book | ||
|
||
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. | ||
|
@@ -547,7 +579,19 @@ Note that, just like for top-level declarations, it was not necessary to specify | |
|
||
## Infix Function Application | ||
|
||
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. | ||
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 `findEntry` function to search an `AddressBook`, one might write: | ||
|
||
```text | ||
> findEntry "John" "Smith" addressBook | ||
``` | ||
|
||
However, this chapter has also included examples of _infix_ functions, such as the `==` function in the definition of `filterEntry`, where the function is put _between_ the arguments. These infix operators are actually defined in the PureScript source as infix aliases for their underlying implementations. For example, `==` is defined as an alias for the prefix `eq` function with the line: | ||
|
||
```haskell | ||
infix 4 eq as == | ||
``` | ||
|
||
Likewise, 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. | ||
|
||
This is equivalent to the usual application `head (filter filterEntry book)` | ||
|
||
|
@@ -567,13 +611,53 @@ But why would we want to use `$` instead of regular function application? The re | |
For example, the following nested function application, which finds the street in the address of an employee's boss: | ||
|
||
```haskell | ||
street (address (boss employee)) | ||
_.street (_.address (_.boss employee)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think something like: book3 = insertEntry john (insertEntry peggy (insertEntry ned emptyBook))
book6 = insertEntry john $ insertEntry peggy $ insertEntry ned emptyBook from the the sequence of examples proposed earlier might be better here. |
||
``` | ||
|
||
becomes (arguably) easier to read when expressed using `$`: | ||
|
||
```haskell | ||
street $ address $ boss employee | ||
_.street $ _.address $ _.boss employee | ||
``` | ||
|
||
Note that neither of the above examples is idiomatic PureScript. Real-world code is more likely to express this as: | ||
```haskell | ||
(boss employee).address.street | ||
``` | ||
or | ||
```haskell | ||
_.boss.address.street employee | ||
``` | ||
|
||
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: | ||
```text | ||
> mod 8 3 | ||
2 | ||
``` | ||
This is fine, but doesn't line up with common usage. Wrapping a prefix function in backticks (\`) lets you use a prefix function in infix position as an operator, e.g., | ||
```text | ||
> 8 `mod` 3 | ||
2 | ||
``` | ||
Likewise, wrapping an operator in parentheses lets you use it as a function in prefix position: | ||
```text | ||
> 8 + 3 | ||
11 | ||
|
||
> (+) 8 3 | ||
11 | ||
``` | ||
This allows for compact definitions of curried (or partially applied) functions based on infix operator functions, such as the `add2` function below: | ||
```text | ||
> add2 = (+) 2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Readers might be asking: "Isn't this both equivalently compact, and easier to type?" add2 = (+) 2
add2 = add 2 <------- I forgot to change the above to this in my original message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see what you mean. Maybe focusing on the inline function use case would help? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, I forgot edit that snippet that I copied. Not sure if your follow-up comment still applies. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Less so, now that you've clarified that you meant that What do you think? Are there some better examples of use cases? Or just delete this bit and just go on to operator sections? (By the way, thanks for the detailed review!) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use cases are pretty obscure, since you can always substitute the original function name. I guess if you wanted to use If an explanation isn't necessary for understanding the exercise code, then it's probably safe to skip. Unfortunately, if a beginner encounters something like We also don't have a well-defined policy on the book's strategy and scope. There are opportunities to reduce duplication, but I also think it's useful to elaborate on concepts from the spec with concrete examples that involve the chapter code (for example with your explanation of property accessors). And I appreciate all your help with improving the book as well. |
||
> add2 4 | ||
6 | ||
``` | ||
Alternatively, operators can be partially applied by surrounding them with parentheses and using `_` as an operand: | ||
shaunplee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```text | ||
> add3 = (3 + _) | ||
> add3 2 | ||
5 | ||
``` | ||
|
||
## Function Composition | ||
|
@@ -612,6 +696,7 @@ I will let you make your own decision which definition is easier to understand, | |
|
||
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. | ||
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`. | ||
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. | ||
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. | ||
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. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's pick a binary function (would also be good to link to that page) to compare and contrast infix vs prefix. For example
insertEntry
.Then we can provide examples like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option for series of examples is:
Described in #261 (comment)
Edit, I see you included some of this material further on.