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 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[]

View File

@ -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].

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 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")]

View File

@ -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}

View File

@ -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.

View File

@ -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