Edit some of the guide with minor improvements in wording and style

This commit is contained in:
Alexis King 2015-11-13 23:14:06 -08:00
parent 6c6a540adf
commit 2b5b59dc47
6 changed files with 42 additions and 37 deletions

View File

@ -4,8 +4,8 @@
This guide is intended for programmers who are familiar with Racket but new to working with lenses or
a certain part of this lens library. It contains a non-authorative introduction to lenses, including
examples of usage and recipies for solving certain kinds of problems. It does not describe all
features this library provides; for a complete API reference, see @secref{lens-reference}.
examples of usage and recipes for solving certain kinds of problems. It does not describe all features
this library provides; for a complete API reference, see @secref{lens-reference}.
@local-table-of-contents[]

View File

@ -4,8 +4,8 @@
@title[#:tag "built-in-lenses" #:style 'toc]{Lenses on Built-In Datatypes}
This library provides @lens-tech{lenses} for most built-in Racket datatypes. In general the name of
each lens corresponds to the name of its accessor function, with @racket[-lens] appended to the end.
This library provides @lens-tech{lenses} for most built-in Racket datatypes. In general, the name of
each lens corresponds to the name of its accessor function with @racket[-lens] appended to the end.
For example, the lens for accessing the first element of a pair is @racket[car-lens], and the lens for
accessing an element of a hash is called @racket[hash-ref-lens].

View File

@ -20,14 +20,14 @@ of these structures by their keys are provided.
Racket hash tables are simple key-value associations, and as a result, they only have one primitive
lens constructor, @racket[hash-ref-lens]. Given a key, it produces a lens which views the value
associated with the lens:
associated with the key:
@(lens-interaction
(lens-transform (hash-ref-lens 'a) (hash 'a "Hello")
(λ (s) (string-append s ", world!"))))
Note that @racket[hash-ref-lens]'s signature differs from that of @racket[hash-ref] in an important
way: it does not accept a "failure result" if the key is missing from the hash. Instead, the lens
way: it does not accept a “failure result” if the key is missing from the hash. Instead, the lens
always throws an error:
@(lens-interaction
@ -56,7 +56,7 @@ define our own hash lens that accepts a default value:
(make-lens (λ (h) (hash-ref h key failure-result))
(λ (h v) (hash-set h key v)))))
With this custom, "naughty" lens, we can actually perform the example from above:
With this custom, “naughty” lens, we can actually perform the example from above:
@(interaction #:eval ref-default-eval
(let ([l (hash-ref-lens/default 'not-a-key "default")]

View File

@ -1,3 +1,4 @@
#lang scribble/manual
@(require scribble/eval
@ -33,8 +34,8 @@ produces a new lens given an index to look up. Abbreviation lenses such as @rack
(lens-transform (list-ref-lens 3) (range 10) sub1)
(lens-transform third-lens (range 10) sub1))
Using @racket[list-ref-lens], it is possible to create a lens that performs indexed lookups for nested
lists:
This is useful, but it only works for flat lists. However, using lens composition, it is possible to
create a lens that performs indexed lookups for nested lists using only @racket[list-ref-lens]:
@(lens-interaction
(define (2d-list-ref-lens x y)
@ -57,7 +58,7 @@ This can also be generalized to @emph{n}-dimensional lists:
'z))
This function is actually provided by @racketmodname[lens] under the name
@racket[list-ref-nested-lens], but it's easy to implement yourself.
@racket[list-ref-nested-lens], but the above example demonstrates that it's really a derived concept.
@subsection{Fetching multiple list values at once}

View File

@ -8,19 +8,19 @@
@title[#:tag "lens-intro"]{Introduction to Lenses}
The @racketmodname[lens] library defines @lens-tech{lenses}, tools for extracting values from
potentially-nested data structures. Lenses are mostly useful when writing in a functional style, such
as the style employed by @emph{How to Design Programs}, in which data structures are immutable and
potentially-nested data structures. Lenses are most useful when writing in a functional style, such as
the style employed by @italic{How to Design Programs}, in which data structures are immutable and
side-effects are kept to a minimum.
@section{What are lenses?}
A @deftech[#:key "lens" #:normalize? #f]{lens} is a value that composes getter and setter functions to
produce a bidirectional view into a data structure. This definition is intentionally broad---lenses
A @deftech[#:key "lens" #:normalize? #f]{lens} is a value that composes a getter and a setter function
to produce a bidirectional view into a data structure. This definition is intentionally broad---lenses
are a very general concept, and they can be applied to almost any kind of value that encapsulates
data. To make the concept more concrete, consider one of Racket's most primitive datatypes, the
@tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{pair}. Pairs are constructed from two values using
@racket[cons]: the first value is retrieved using @racket[car], and the second is retrieved using
@racket[cdr].
the @racket[cons] function; the first value can then be retrieved using @racket[car], and the second
can be retrieved using @racket[cdr].
@(interaction
(define p (cons 1 2))
@ -28,11 +28,11 @@ data. To make the concept more concrete, consider one of Racket's most primitive
(car p)
(cdr p))
With these three primitives, it's very easy to create new pairs and extract values from existing
pairs. However, it's a little bit harder to update a single field from an existing pair. In a
traditional Scheme, this could be accomplished by using @racket[set-car!] or @racket[set-cdr!], but
these @emph{mutate} the original pair. To remain functional, we want to produce an @emph{entirely new}
pair with one of the fields updated.
With these three primitives, it's very easy to create new pairs and subsequently extract values from
them. However, it's a little bit harder to update a single field in an existing pair. In a traditional
Scheme, this could be accomplished by using @racket[set-car!] or @racket[set-cdr!], but these
@emph{mutate} the original pair. To remain functional, we want to produce an @emph{entirely new} pair
with one of the fields updated.
Fortunately, this is quite easy to implement in Racket:
@ -50,8 +50,8 @@ Fortunately, this is quite easy to implement in Racket:
This means that each field now has a pair of getters and setters: @racket[car]/@racket[set-car] and
@racket[cdr]/@racket[set-cdr]. A lens just wraps up each of these pairs of functions into a single
value, so instead of having four functions, we would just have @racket[car-lens] and
@racket[cdr-lens]. And in fact, using the functions we've just written, we can implement these lenses
value, so instead of having four functions, we would just have two lenses: @racket[car-lens] and
@racket[cdr-lens]. In fact, using the functions we've just written, we can implement these lenses
ourselves.
@(interaction #:eval introduction-eval
@ -65,21 +65,25 @@ To use a lens's getter function, use @racket[lens-view]. To use the setter funct
(lens-view car-lens (cons 1 2))
(lens-set car-lens (cons 1 2) 'x))
This, of course, isn't very useful, since we could just use the functions on their own. One thing we
do get when using lenses is @racket[lens-transform]. This allows you to provide a procedure which will
update the “view” based on its existing value. For example, we could increment one element in a pair:
This, of course, isn't very useful, since we could just use the functions on their own. One extra
thing we @emph{do} get for free when using lenses is @racket[lens-transform]. This allows you to
provide a procedure which will update the “view” based on its existing value. For example, we could
increment one element in a pair:
@(interaction #:eval introduction-eval
(lens-transform cdr-lens (cons 1 2) add1))
While that's kind of cool, it still probably isn't enough to justify using lenses instead of just
using functions.
@section[#:style 'quiet]{Why use lenses?}
So far, lenses just seem like a way to group getters and setters, and as we've seen, that's all they
really are. However, on their own, this wouldn't be very useful. Using @racket[(car _p)] is a lot
easier than using @racket[(lens-view car-lens _p)].
So far, lenses just seem like a way to group getters and setters, and as we've seen, that's really all
they are. However, on their own, this wouldn't be very useful. Using @racket[(car _p)] is a lot easier
than using @racket[(lens-view car-lens _p)].
Using plain functions starts to get a lot harder, though, once you start nesting data structures. For
example, consider a tree structure with pairs nested inside of pairs:
example, consider a tree constructed by nesting pairs inside of pairs:
@(interaction #:eval introduction-eval
(define tree (cons (cons 'a 'b)
@ -113,8 +117,8 @@ than that. How can we solve it?
@other-reference-note{For more ways to construct compound lenses, see @secref{composing-lenses}.}
In order to solve this problem, we can use @emph{lens composition}, which is similar to function
composition but extended to lenses. Just as we can create a compound getter with the expression
@racket[(compose cdr car)], we can create a compound lens with the expression
composition but extended to lenses. Just as we can create a compound getter function with the
expression @racket[(compose cdr car)], we can create a compound lens with the expression
@racket[(lens-compose cdr-lens car-lens)]. With this, we produce an entirely new lens that can be used
with @racket[lens-view], @racket[lens-set], and @racket[lens-transform], all of which do what you
would expect:
@ -125,7 +129,7 @@ would expect:
(lens-set cdar-lens tree 'x)
(lens-transform cdar-lens tree symbol->string))
Now it may begin to crystallize why lenses are useful: they make it possible to not just get at but
to actually functionally update and transform values within deeply-nested data structures. Since they
are composable, it is easy to create lenses that can traverse any set of structures with nothing but
a small set of primitives. This library provides those primitives.
Now the reason lenses are useful may begin to crystallize: they make it possible to not just get at
but to actually functionally update and transform values within deeply-nested data structures. Since
they are composable, it is easy to create lenses that can traverse any set of structures with nothing
but a small set of primitives. This library provides those primitives.

View File

@ -16,7 +16,7 @@
Sometimes the existing set of lenses isn't enough. Perhaps you have a particularly unique data
structure, and you want to create a lens for it. Perhaps you just want to provide lenses for your
custom data structures, and struct lenses aren't good enough. In that case, it's always possible to
custom data structures, and struct lenses are insufficient. In that case, it's always possible to
fall back on the primitive lens constructor, @racket[make-lens].
The @racket[make-lens] constructor is simple---it creates a new lens from a getter function and a