Add guide entries for most of the built-in datatype lenses

This commit is contained in:
Alexis King 2015-11-13 14:31:04 -08:00
parent e0ab371d2f
commit a361070ee0
13 changed files with 236 additions and 10 deletions

View File

@ -2,7 +2,7 @@
@(require "../doc-util/main.rkt")
@title{Lens Laws}
@title[#:tag "laws"]{Lens Laws}
While @racket[make-lens] allows lenses to be constructed
from arbitrary getters and setters, these getters and setters

View File

@ -3,7 +3,9 @@
@(require "doc-util/main.rkt")
@title{Dict lenses}
@title[#:tag "dict-reference"]{Dict lenses}
@see-guide-note["dict-guide"]{dictionary lenses}
@defproc[(dict-ref-lens [key any/c]) lens?]{
Returns a lens for viewing the value mapped to @racket[key] in a dict.

View File

@ -16,6 +16,7 @@ for-label
racket/stream
racket/set
racket/contract
racket/function
for-syntax
racket/base
syntax/parse

View File

@ -5,7 +5,7 @@
(only-in scribble/core style)
setup/collects)
(provide other-reference-note)
(provide other-reference-note see-guide-note see-reference-note)
(define css-resource
(make-css-addition
@ -23,3 +23,11 @@
(define (other-reference-note . pre-content)
(margin-note (flexible-container finger (apply flexible-element pre-content))))
(define (see-guide-note tag . pre-content)
@other-reference-note{
@seclink[tag]{The Lens Guide} has additional examples of @|pre-content|.})
(define (see-reference-note tag . pre-content)
@other-reference-note{
@seclink[tag]{The Lens Reference} has additional information on @|pre-content|.})

View File

@ -1,8 +1,11 @@
#lang scribble/manual
@(require "../doc-util/scribble-include-no-subsection.rkt")
@(require "../doc-util/main.rkt"
"../doc-util/scribble-include-no-subsection.rkt")
@title{Hash Lenses}
@title[#:tag "hash-reference"]{Hash Lenses}
@see-guide-note["hash-guide"]{hash lenses}
@scribble-include/no-subsection["ref.scrbl"]
@scribble-include/no-subsection["nested.scrbl"]

View File

@ -1,6 +1,10 @@
#lang scribble/manual
@title{Pair and List Lenses}
@(require "../doc-util/main.rkt")
@title[#:tag "pair-list-reference"]{Pair and List Lenses}
@see-guide-note["pair-list-guide"]{pair and list lenses}
@include-section["car-cdr.scrbl"]
@include-section["list-ref-take-drop.scrbl"]

View File

@ -10,3 +10,4 @@ features this library provides; for a complete API reference, see @secref{lens-r
@local-table-of-contents[]
@include-section["guide/introduction.scrbl"]
@include-section["guide/built-in.scrbl"]

View File

@ -9,6 +9,14 @@
@(define-syntax-rule (lens-interaction expr ...)
(interaction #:eval (make-lens-eval) expr ...))
@title[#:tag "builtin-lenses"]{Lenses on Built-In Datatypes}
@title[#:tag "builtin-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.
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].
@local-table-of-contents[]
@include-section["built-in/ordered.scrbl"]
@include-section["built-in/key-value.scrbl"]

View File

@ -0,0 +1,90 @@
#lang scribble/manual
@(require scribble/eval
"../../../doc-util/main.rkt")
@(define make-lens-eval
(make-eval-factory '(racket/base lens)))
@(define-syntax-rule (lens-interaction expr ...)
(interaction #:eval (make-lens-eval) expr ...))
@title[#:tag "key-value-lenses"]{Lenses on Key-Value Data}
Many Racket data structures hold values that correspond to a given key. Lenses for accessing elements
of these structures by their keys are provided.
@section[#:tag "hash-guide"]{Hash Tables}
@see-reference-note["hash-reference"]{hash lenses}
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:
@(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
always throws an error:
@(lens-interaction
(lens-view (hash-ref-lens 'not-a-key) (hash)))
This may seem inconvenient, but this limitation is by design---supporting a failure result would
violate one of the @seclink["laws"]{lens laws}. Specifically, “get-set consistency” would no longer
hold. Consider this example:
@(racketblock
(let ([l (hash-ref-lens 'not-a-key "default")]
[h (hash)])
(lens-set l h (lens-view l h))))
If @racket[hash-ref-lens] accepted a default value, then the above expression would produce a new hash
that was not @racket[equal?] to the original target. Enforcing this property makes lenses easier to
reason about, just as ensuring purity makes functions easier to reason about.
Of course, sometimes breaking purity is the easiest way to solve a problem, and similarly, sometimes
breaking the lens laws is okay (though it should be avoided if possible). We could, if we wished,
define our own hash lens that accepts a default value:
@(define ref-default-eval (make-lens-eval))
@(interaction #:eval ref-default-eval
(define (hash-ref-lens/default key failure-result)
(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:
@(interaction #:eval ref-default-eval
(let ([l (hash-ref-lens/default 'not-a-key "default")]
[h (hash)])
(lens-set l h (lens-view l h))))
In addition to @racket[hash-ref-lens], @racket[hash-ref-nested-lens] is provided, which assists in
fetching values from nested hashes. It is defined in terms of @racket[hash-ref-lens] and
@racket[lens-compose], so it is just a shorter way of expressing the same concept:
@(lens-interaction
(lens-set (hash-ref-nested-lens 'a 'b 'c)
(hash 'a (hash 'b (hash 'c "foo")))
"bar"))
@section[#:tag "dict-guide"]{Dictionaries}
@see-reference-note["dict-reference"]{dictionary lenses}
Racket @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{dictionaries} provide a generic
interface for many kinds of key-value data-structures. They encompass hash tables, association lists,
user-defined dictionaries, and even integer-keyed structures like vectors.
In practice, dictionary lenses work identically to lenses on hashes. The @racket[dict-ref-lens]
lens constructor creates a lens with a view that is the value associated with the lens's key.
@(lens-interaction
(lens-transform (dict-ref-lens 'b)
'((a . 1)
(b . 2))
(λ (x) (* x 2))))

View File

@ -0,0 +1,107 @@
#lang scribble/manual
@(require scribble/eval
"../../../doc-util/main.rkt")
@(define make-lens-eval
(make-eval-factory '(racket/base racket/function racket/list racket/stream lens)))
@(define-syntax-rule (lens-interaction expr ...)
(interaction #:eval (make-lens-eval) expr ...))
@title[#:tag "ordered-data-lenses"]{Lenses on Ordered Data}
Many Racket data structures hold @emph{ordered} or @emph{sequential} values. Lenses for accessing
elements of these structures by index are provided.
@section[#:tag "pair-list-guide"]{Pairs and Lists}
@see-reference-note["pair-list-reference"]{pair and list lenses}
The two primitive pair lenses are @racket[car-lens] and @racket[cdr-lens]:
@(lens-interaction
(lens-transform car-lens '(1 . 2) (curry * 2))
(lens-transform cdr-lens '(1 . 2) (curry * 2)))
Obviously, these also work with lists, but most of the time, it's easier to use list-specific lenses.
For arbitrary access to elements within a list, use the @racket[list-ref-lens] lens constructor, which
produces a new lens given an index to look up. Abbreviation lenses such as @racket[first-lens] and
@racket[second-lens] are provided for common use-cases:
@(lens-interaction
(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:
@(lens-interaction
(define (2d-list-ref-lens x y)
(lens-compose (list-ref-lens x)
(list-ref-lens y)))
(lens-set (2d-list-ref-lens 1 2)
'((1 2 3)
(4 5 6)
(7 8 9))
0))
This can also be generalized to @emph{n}-dimensional lists:
@(lens-interaction
(define (list-ref-lens* . indicies)
(apply lens-compose (map list-ref-lens indicies)))
(lens-set (list-ref-lens* 0 1 0)
'(((a b) (c d))
((e f) (g h)))
'z))
This function is actually provided by @racketmodname[lens] under the name
@racket[list-ref-nested-lens], but it's easy to implement yourself.
@subsection{Fetching multiple list values at once}
Sometimes it can be useful to fetch multiple values from a list with a single lens. This can be done
with @racket[lens-join/list], which combines multiple lenses whose target is a single value and
produces a new lens whose view is all of those values.
@(lens-interaction
(define first-two-lens (lens-join/list first-lens second-lens))
(lens-view first-two-lens '(1 2 3 4))
(lens-set first-two-lens '(1 2 3 4) '(a b))
(lens-transform first-two-lens '(1 2 3 4) (curry map sub1)))
This can be useful to implement a form of information hiding, in which only a portion of a list is
provided to client code, but the result can still be used to update the original list.
@section[#:tag "vectors-strings-guide"]{Vectors and Strings}
@other-reference-note{
The @secref["vectors-reference"] and @secref["strings-reference"] sections in The Lens Reference
have additional information on vector and string lenses, respectively.}
Lenses for random-access retrieval and functional update on vectors and strings are similar to the
lenses provided for lists, but unlike lists, they are truly random-access. The
@racket[vector-ref-lens] and @racket[string-ref-lens] lens constructors produce random-access lenses,
and @racket[lens-join/vector] and @racket[lens-join/string] combine multiple lenses with vector or
string targets.
@(lens-interaction
(lens-transform (vector-ref-lens 1) #("a" "b" "c") string->symbol)
(lens-transform (string-ref-lens 3) "Hello!" char-upcase))
@section[#:tag "streams-guide"]{Streams}
@see-reference-note["streams-reference"]{stream lenses}
Racket's @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{streams} contain ordered data,
much like lists, but unlike lists, they are @emph{lazy}. Lenses on streams are similarly lazy, only
forcing the stream up to what is necessary. This allows stream lenses to successfully operate on
infinite streams.
@(lens-interaction
(lens-view (stream-ref-lens 10)
(stream-map (curry expt 2) (in-naturals))))
Keep in mind that since @racket[lens-transform] is strict, using it to update a value within a stream
will force the stream up to the position of the element being modified.

View File

@ -3,7 +3,9 @@
@(require "doc-util/main.rkt")
@title{Stream Lenses}
@title[#:tag "streams-reference"]{Stream Lenses}
@see-guide-note["streams-guide"]{stream lenses}
@defthing[stream-first-lens lens?]{
A lens for viewing the first element of a stream.

View File

@ -2,7 +2,7 @@
@(require "doc-util/main.rkt")
@title{String Lenses}
@title[#:tag "strings-reference"]{String Lenses}
@defproc[(string-ref-lens [i exact-nonnegative-integer?]) lens?]{
Returns a lens for viewing the @racket[i]th character of a string.

View File

@ -2,7 +2,7 @@
@(require "../doc-util/scribble-include-no-subsection.rkt")
@title{Vector lenses}
@title[#:tag "vectors-reference"]{Vector lenses}
@scribble-include/no-subsection["ref.scrbl"]
@scribble-include/no-subsection["nested.scrbl"]