Edit some of the guide with minor improvements in wording and style
This commit is contained in:
parent
6c6a540adf
commit
2b5b59dc47
|
@ -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[]
|
||||
|
||||
|
|
|
@ -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].
|
||||
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user