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
|
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
|
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
|
examples of usage and recipes for solving certain kinds of problems. It does not describe all features
|
||||||
features this library provides; for a complete API reference, see @secref{lens-reference}.
|
this library provides; for a complete API reference, see @secref{lens-reference}.
|
||||||
|
|
||||||
@local-table-of-contents[]
|
@local-table-of-contents[]
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
@title[#:tag "built-in-lenses" #:style 'toc]{Lenses on Built-In Datatypes}
|
@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
|
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.
|
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
|
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].
|
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
|
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
|
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-interaction
|
||||||
(lens-transform (hash-ref-lens 'a) (hash 'a "Hello")
|
(lens-transform (hash-ref-lens 'a) (hash 'a "Hello")
|
||||||
(λ (s) (string-append s ", world!"))))
|
(λ (s) (string-append s ", world!"))))
|
||||||
|
|
||||||
Note that @racket[hash-ref-lens]'s signature differs from that of @racket[hash-ref] in an important
|
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:
|
always throws an error:
|
||||||
|
|
||||||
@(lens-interaction
|
@(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))
|
(make-lens (λ (h) (hash-ref h key failure-result))
|
||||||
(λ (h v) (hash-set h key v)))))
|
(λ (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
|
@(interaction #:eval ref-default-eval
|
||||||
(let ([l (hash-ref-lens/default 'not-a-key "default")]
|
(let ([l (hash-ref-lens/default 'not-a-key "default")]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
|
|
||||||
@(require scribble/eval
|
@(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 (list-ref-lens 3) (range 10) sub1)
|
||||||
(lens-transform third-lens (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
|
This is useful, but it only works for flat lists. However, using lens composition, it is possible to
|
||||||
lists:
|
create a lens that performs indexed lookups for nested lists using only @racket[list-ref-lens]:
|
||||||
|
|
||||||
@(lens-interaction
|
@(lens-interaction
|
||||||
(define (2d-list-ref-lens x y)
|
(define (2d-list-ref-lens x y)
|
||||||
|
@ -57,7 +58,7 @@ This can also be generalized to @emph{n}-dimensional lists:
|
||||||
'z))
|
'z))
|
||||||
|
|
||||||
This function is actually provided by @racketmodname[lens] under the name
|
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}
|
@subsection{Fetching multiple list values at once}
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,19 @@
|
||||||
@title[#:tag "lens-intro"]{Introduction to Lenses}
|
@title[#:tag "lens-intro"]{Introduction to Lenses}
|
||||||
|
|
||||||
The @racketmodname[lens] library defines @lens-tech{lenses}, tools for extracting values from
|
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
|
potentially-nested data structures. Lenses are most useful when writing in a functional style, such as
|
||||||
as the style employed by @emph{How to Design Programs}, in which data structures are immutable and
|
the style employed by @italic{How to Design Programs}, in which data structures are immutable and
|
||||||
side-effects are kept to a minimum.
|
side-effects are kept to a minimum.
|
||||||
|
|
||||||
@section{What are lenses?}
|
@section{What are lenses?}
|
||||||
|
|
||||||
A @deftech[#:key "lens" #:normalize? #f]{lens} is a value that composes getter and setter functions to
|
A @deftech[#:key "lens" #:normalize? #f]{lens} is a value that composes a getter and a setter function
|
||||||
produce a bidirectional view into a data structure. This definition is intentionally broad---lenses
|
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
|
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
|
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
|
@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
|
the @racket[cons] function; the first value can then be retrieved using @racket[car], and the second
|
||||||
@racket[cdr].
|
can be retrieved using @racket[cdr].
|
||||||
|
|
||||||
@(interaction
|
@(interaction
|
||||||
(define p (cons 1 2))
|
(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)
|
(car p)
|
||||||
(cdr p))
|
(cdr p))
|
||||||
|
|
||||||
With these three primitives, it's very easy to create new pairs and extract values from existing
|
With these three primitives, it's very easy to create new pairs and subsequently extract values from
|
||||||
pairs. However, it's a little bit harder to update a single field from an existing pair. In a
|
them. However, it's a little bit harder to update a single field in an existing pair. In a traditional
|
||||||
traditional Scheme, this could be accomplished by using @racket[set-car!] or @racket[set-cdr!], but
|
Scheme, this could be accomplished by using @racket[set-car!] or @racket[set-cdr!], but these
|
||||||
these @emph{mutate} the original pair. To remain functional, we want to produce an @emph{entirely new}
|
@emph{mutate} the original pair. To remain functional, we want to produce an @emph{entirely new} pair
|
||||||
pair with one of the fields updated.
|
with one of the fields updated.
|
||||||
|
|
||||||
Fortunately, this is quite easy to implement in Racket:
|
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
|
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
|
@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
|
value, so instead of having four functions, we would just have two lenses: @racket[car-lens] and
|
||||||
@racket[cdr-lens]. And in fact, using the functions we've just written, we can implement these lenses
|
@racket[cdr-lens]. In fact, using the functions we've just written, we can implement these lenses
|
||||||
ourselves.
|
ourselves.
|
||||||
|
|
||||||
@(interaction #:eval introduction-eval
|
@(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-view car-lens (cons 1 2))
|
||||||
(lens-set car-lens (cons 1 2) 'x))
|
(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
|
This, of course, isn't very useful, since we could just use the functions on their own. One extra
|
||||||
do get when using lenses is @racket[lens-transform]. This allows you to provide a procedure which will
|
thing we @emph{do} get for free when using lenses is @racket[lens-transform]. This allows you to
|
||||||
update the “view” based on its existing value. For example, we could increment one element in a pair:
|
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
|
@(interaction #:eval introduction-eval
|
||||||
(lens-transform cdr-lens (cons 1 2) add1))
|
(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?}
|
@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
|
So far, lenses just seem like a way to group getters and setters, and as we've seen, that's really all
|
||||||
really are. However, on their own, this wouldn't be very useful. Using @racket[(car _p)] is a lot
|
they are. However, on their own, this wouldn't be very useful. Using @racket[(car _p)] is a lot easier
|
||||||
easier than using @racket[(lens-view car-lens _p)].
|
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
|
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
|
@(interaction #:eval introduction-eval
|
||||||
(define tree (cons (cons 'a 'b)
|
(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}.}
|
@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
|
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
|
composition but extended to lenses. Just as we can create a compound getter function with the
|
||||||
@racket[(compose cdr car)], we can create a compound lens with the expression
|
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
|
@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
|
with @racket[lens-view], @racket[lens-set], and @racket[lens-transform], all of which do what you
|
||||||
would expect:
|
would expect:
|
||||||
|
@ -125,7 +129,7 @@ would expect:
|
||||||
(lens-set cdar-lens tree 'x)
|
(lens-set cdar-lens tree 'x)
|
||||||
(lens-transform cdar-lens tree symbol->string))
|
(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
|
Now the reason lenses are useful may begin to crystallize: they make it possible to not just get at
|
||||||
to actually functionally update and transform values within deeply-nested data structures. Since they
|
but to actually functionally update and transform values within deeply-nested data structures. Since
|
||||||
are composable, it is easy to create lenses that can traverse any set of structures with nothing but
|
they are composable, it is easy to create lenses that can traverse any set of structures with nothing
|
||||||
a small set of primitives. This library provides those primitives.
|
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
|
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
|
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].
|
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
|
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