2 The Lens Reference
2.1 Lens Operations
(require lens/common) | package: lens-common |
2.1.1 Core Lens Forms
2.1.1.1 Lens Construction
> (define (set-first lst v) (list* v (rest lst))) > (set-first '(1 2 3) 'a) '(a 2 3)
> (define first-lens (make-lens first set-first)) > (lens-view first-lens '(1 2 3)) 1
> (lens-set first-lens '(1 2 3) 'a) '(a 2 3)
syntax
(let-lens (view-id context-id) lens-expr target-expr body ...)
> (let-lens (view context) first-lens '(1 2 3) (printf "View is ~a\n" view) (context 'a)) View is 1
'(a 2 3)
2.1.1.2 Viewing and Setting
> (lens-view first-lens '(1 2 3)) 1
procedure
(lens-set lens target new-view) → target/c
lens : lens? target : target/c new-view : view/c
> (lens-set first-lens '(1 2 3) 'a) '(a 2 3)
procedure
(lens-view/list target lens ...) → view/c
target : target/c lens : lens?
> (lens-view/list '(a b c d e f g) first-lens fourth-lens fifth-lens) '(a d e)
procedure
(lens-set/list target lens new-view ... ...) → target/c
target : target/c lens : lens? new-view : view/c
> (lens-set/list '(1 2 3 4 5) first-lens 10 third-lens 300) '(10 2 300 4 5)
> (lens-set/list '(1 2 3) first-lens 'a first-lens 'b) '(b 2 3)
2.1.1.3 Lens Laws
While make-lens allows lenses to be constructed from arbitrary getters and setters, these getters and setters should obey some algebraic laws in order for a lens to be a proper lens. A lens that does not obey the lens laws for all values it can focus on is an improper lens. The lens laws formalize some standard intuitions for how getters and setters "ought" to work. The laws for lenses are:
- Purity - Viewing and setting with a lens L must both be pure functions. Formally, the two functionsmust be pure for tagets and views of L.
- Set-Get Consistency - For all targets and views, setting a target and then viewing it returns the set value. Formally, given a target T of a lens L, the functionmust be identical to the identity function for views of L with respect to a reasonable definition of equality for views of L.
- Get-Set Consistency - For all targets and views, getting a view of a target and then setting the target’s view to the view you just got does nothing. Formally, given a target T of a lens L, the expressionmust be equal to T with respect to a reasonable definition of equality for targets of L.
- Last Set Wins - For all targets and views, if you set the same target to two different views, the one you set last applies. Formally, given a target T of a lens L and two views v-first and v-second of L, the expressionmust be equal to
(lens-set L T v-second)
with respect to a reasonable definition of equality for targets of L.
For those familiar with getters and setters in OO languages, none of these should be surprising other than the requirement that lenses be pure. The purity of lenses allows them to be composed more effectively and reasoned about more easily than an impure equivalent of lenses.
All lenses provided by this library are proper unless otherwise stated. There is no enforcement or contract that lenses constructed with functions from this library will always be proper, but individual functions may provide conditional guarantees about their interactions with improper lenses and the lens laws.
2.1.1.4 Transforming Values With Lenses
procedure
(lens-transform lens target transformer) → target/c
lens : lens? target : target/c transformer : (-> view/c view/c)
> (lens-transform first-lens '(1 2 3) number->string) '("1" 2 3)
procedure
(lens-transform/list target lens transformer ... ...) → target/c target : target/c lens : lens? transformer : (-> view/c view/c)
> (lens-transform/list '(1 2 3 4 5) first-lens number->string third-lens (λ (x) (* 100 x))) '("1" 2 300 4 5)
2.1.1.5 Lens Contracts
> (define contracted-car-lens (invariant-assertion (lens/c pair? number?) car-lens)) > (lens-view contracted-car-lens (cons 1 2)) 1
> (lens-view contracted-car-lens 'not-a-pair) contracted-car-lens: assertion violation
expected: pair?
given: 'not-a-pair
in: the 2nd argument of
a part of the or/c of
method lens-view
(lens/c pair? number?)
contract from: invariant-assertion
at: eval:7.0
> (lens-view contracted-car-lens (cons 'not-a-number 2)) contracted-car-lens: assertion violation
expected: number?
given: 'not-a-number
in: the range of
a part of the or/c of
method lens-view
(lens/c pair? number?)
contract from: invariant-assertion
at: eval:7.0
> (lens-set contracted-car-lens (cons 1 2) 'not-a-number) contracted-car-lens: assertion violation
expected: number?
given: 'not-a-number
in: the 3rd argument of
a part of the or/c of
method lens-set
(lens/c pair? number?)
contract from: invariant-assertion
at: eval:7.0
2.1.2 Joining and Composing Lenses
procedure
(lens-compose lens ...) → lens?
lens : lens? Composes the given lenses together into one compound lens. The compound lens operates similarly to composed functions do in that the last lens is the first lens the compound lens’s target is viewed through. Each successive lens "zooms in" to a more detailed view. When called with no arguments, lens-compose produces the identity lens.Examples:
> (define first-of-second-lens (lens-compose first-lens second-lens)) > (lens-view first-of-second-lens '((1 a) (2 b) (3 c))) 2
> (lens-set first-of-second-lens '((1 a) (2 b) (3 c)) 200) '((1 a) (200 b) (3 c))
value
The identity lens. Performs no destructuring at all - it’s view is the target itself. For all lenses, both (lens-compose lens identity-lens) and (lens-compose identity-lens lens) are equivalent to lens.Examples:
> (lens-view identity-lens 4) 4
> (lens-set identity-lens 4 'a) 'a
procedure
(lens-thrush lens ...) → lens?
lens : lens? Like lens-compose, but each lens is combined in the opposite order. That is, the first lens is the first lens that the compound lens’s target is viewed through.Examples:
> (define first-of-second-lens (lens-thrush second-lens first-lens)) > (lens-view first-of-second-lens '((1 a) (2 b) (3 c))) 2
> (lens-set first-of-second-lens '((1 a) (2 b) (3 c)) 200) '((1 a) (200 b) (3 c))
2.2 Lenses for different types of data
2.2.1 Pair and List Lenses
(require lens/data/list) | package: lens-data |
The Lens Guide has additional examples of pair and list lenses.
2.2.1.1 Pair lenses
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
> (cdaddr '(9 8 (6 5 4 3 2 1) 7)) '(5 4 3 2 1)
> (lens-view cdaddr-lens '(9 8 (6 5 4 3 2 1) 7)) '(5 4 3 2 1)
> (lens-transform cdaddr-lens '(9 8 (6 5 4 3 2 1) 7) list->vector) '(9 8 (6 . #(5 4 3 2 1)) 7)
2.2.1.2 List lenses
procedure
(list-ref-lens n) → lens?
n : exact-nonnegative-integer?
> (lens-view (list-ref-lens 3) '(a b c d e f g h)) 'd
> (lens-set (list-ref-lens 1) '(a b c d e f g h) 'FOO) '(a FOO c d e f g h)
value
value
value
value
value
value
value
value
value
value
> (lens-view third-lens '(a b c d)) 'c
> (lens-view (lens-compose second-lens fourth-lens) '((a 1) (b 2) (c 3) (d 4))) 4
procedure
(list-ref-nested-lens index ...) → lens?
index : exact-nonnegative-integer? Constructs a lens that views into a tree made from nested lists. Indexing starts from zero in the same was as list-ref-lens.Examples:
> (define first-of-second-lens (list-ref-nested-lens 1 0)) > (lens-view first-of-second-lens '(1 (a b c) 2 3)) 'a
> (lens-set first-of-second-lens '(1 (a b c) 2 3) 'foo) '(1 (foo b c) 2 3)
procedure
(list-refs-lens index ...) → lens?
index : exact-nonnegative-integer? Constructs a lens that views each index item in a list. Indexing starts from zero in the same was as list-ref-lens.Examples:
> (define 1-5-6-lens (list-refs-lens 1 5 6)) > (lens-view 1-5-6-lens '(a b c d e f g)) '(b f g)
> (lens-set 1-5-6-lens '(a b c d e f g) '(1 2 3)) '(a 1 c d e 2 3)
2.2.1.3 Joining lenses to view lists
procedure
(lens-join/list lens ...) → lens?
lens : lens?
The joined lens only follows the lens laws if the views of the argument lenses don’t overlap. Views of the lenses overlap when setting one can change the view of another lens.
> (define first-third-fifth-lens (lens-join/list first-lens third-lens fifth-lens)) > (lens-view first-third-fifth-lens '(a b c d e f)) '(a c e)
> (lens-set first-third-fifth-lens '(a b c d e f) '(1 2 3)) '(1 b 2 d 3 f)
2.2.1.4 Association List Lenses
procedure
(assoc-lens key [#:is-equal? key-equal?]) → lens?
key : any/c key-equal? : (-> any/c any/c any/c) = equal?
> (define assoc-a-lens (assoc-lens 'a)) > (define some-assoc-list '((a . 1) (b . 2) (c . 3))) > (lens-view assoc-a-lens some-assoc-list) 1
> (lens-set assoc-a-lens some-assoc-list 100) '((a . 100) (b . 2) (c . 3))
> (define assoc-foo-lens (assoc-lens "foo" #:is-equal? string=?)) > (lens-view assoc-foo-lens '(("bar" . 1) ("foo" . 2) ("baz" . 3))) 2
2.2.2 Hash Lenses
(require lens/data/hash) | package: lens-data |
The Lens Guide has additional examples of hash lenses.
procedure
(hash-ref-lens key) → lens?
key : any/c Constructs a lens that targets hashes and views the value of key.
procedure
(hash-ref-nested-lens key ...) → lens?
key : any/c Contructs a lens that targets hashes with nested hashes as values and views the value obtained by using each key in order.
procedure
(hash-pick-lens key ...) → lens?
key : any/c Creates a lens that views a subset of the target hash-table with the given keys. The view, is another hash-table with only the given keys and their corrosponding values in the target hash-table.Examples:
> (lens-view (hash-pick-lens 'a 'c) (hash 'a 1 'b 2 'c 3)) '#hash((a . 1) (c . 3))
> (lens-set (hash-pick-lens 'a 'c) (hash 'a 1 'b 2 'c 3) (hash 'a 4 'c 5)) '#hash((a . 4) (b . 2) (c . 5))
procedure
(lens-join/hash key lens ... ...) → lens?
key : any/c lens : lens? Constructs a lens that combines the view of each lens into a hash of views with keys as the hash keys. In the same manner as lens-join/list, if lenses share views later lenses take precedence when setting.Examples:
> (define first-third-hash-lens (lens-join/hash 'first first-lens 'third third-lens)) > (lens-view first-third-hash-lens '(1 2 3)) '#hash((first . 1) (third . 3))
> (lens-set first-third-hash-lens '(1 2 3) (hash 'first 100 'third 200)) '(100 2 200)
2.2.3 Struct Lenses
(require lens/data/struct) | package: lens-data |
The Lens Guide has additional examples of struct lenses.
syntax
(struct-lens struct-id field-id)
Returns a lens for viewing the field-id field of a struct-id instance.Examples:
> (struct foo (a b c) #:transparent) > (lens-view (struct-lens foo a) (foo 1 2 3)) 1
> (lens-set (struct-lens foo a) (foo 1 2 3) 100) (foo 100 2 3)
syntax
(define-struct-lenses struct-id)
Given a struct-id, defines a lens for each of its fields.Examples:
> (struct foo (a b c) #:transparent) > (define-struct-lenses foo) > (lens-view foo-a-lens (foo 1 2 3)) 1
> (lens-set foo-a-lens (foo 1 2 3) 100) (foo 100 2 3)
syntax
(struct/lens struct-id (field-spec ...) struct-option ...)
Equivalent to struct and define-struct-lenses combined.Examples:
> (struct/lens foo (a b c) #:transparent) > (lens-view foo-a-lens (foo 1 2 3)) 1
> (lens-set foo-a-lens (foo 1 2 3) 100) (foo 100 2 3)
2.2.4 Vector lenses
(require lens/data/vector) | package: lens-data |
procedure
(vector-ref-lens i) → lens?
i : exact-nonnegative-integer? Returns a lens that views an element of a vector.Examples:
> (lens-view (vector-ref-lens 2) #(a b c d)) 'c
> (lens-set (vector-ref-lens 2) #(a b c d) "sea") '#(a b "sea" d)
procedure
(vector-ref-nested-lens i ...) → lens?
i : exact-nonnegative-integer? Examples:
> (lens-view (vector-ref-nested-lens 2 1) #(a b #(s i) d)) 'i
> (lens-set (vector-ref-nested-lens 2 1) #(a b #(s i) d) "eye") '#(a b #(s "eye") d)
procedure
(vector-pick-lens i ...) → lens?
i : exact-nonnegative-integer? Examples:
> (define 1-5-6-lens (vector-pick-lens 1 5 6)) > (lens-view 1-5-6-lens #(a b c d e f g)) '#(b f g)
> (lens-set 1-5-6-lens #(a b c d e f g) #(1 2 3)) '#(a 1 c d e 2 3)
procedure
(lens-join/vector lens ...) → lens?
lens : lens? Like lens-join/list, except the view is a vector, not a list.Examples:
> (define vector-first-third-fifth-lens (lens-join/vector first-lens third-lens fifth-lens)) > (lens-view vector-first-third-fifth-lens '(a b c d e f)) '#(a c e)
> (lens-set vector-first-third-fifth-lens '(a b c d e f) #(1 2 3)) '(1 b 2 d 3 f)
2.2.5 String Lenses
(require lens/data/string) | package: lens-data |
procedure
(string-ref-lens i) → lens?
i : exact-nonnegative-integer? Returns a lens for viewing the ith character of a string.Examples:
> (lens-view (string-ref-lens 2) "abcdef") #\c
> (lens-set (string-ref-lens 2) "abcdef" #\C) "abCdef"
procedure
(string-pick-lens i) → lens?
i : exact-nonnegative-integer? Examples:
> (define 1-5-6-lens (string-pick-lens 1 5 6)) > (lens-view 1-5-6-lens "abcdefg") "bfg"
> (lens-set 1-5-6-lens "abcdefg" "BFG") "aBcdeFG"
procedure
(lens-join/string lens ...) → lens?
lens : lens? Like lens-join/list, except the view is a string, not a list. Each lens argument must return a char? as a view.Examples:
> (define string-first-third-fifth-lens (lens-join/string first-lens third-lens fifth-lens)) > (lens-view string-first-third-fifth-lens '(#\a #\b #\c #\d #\e #\f)) "ace"
> (lens-set string-first-third-fifth-lens '(#\a #\b #\c #\d #\e #\f) "ACE") '(#\A #\b #\C #\d #\E #\f)
2.2.6 Stream Lenses
(require lens/data/stream) | package: lens-data |
The Lens Guide has additional examples of stream lenses.
value
> (lens-view stream-first-lens (stream 1 2 3)) 1
> (stream->list (lens-set stream-first-lens (stream 1 2 3) 'a)) '(a 2 3)
value
> (stream->list (lens-view stream-rest-lens (stream 1 2 3))) '(2 3)
> (stream->list (lens-set stream-rest-lens (stream 1 2 3) (stream 200 300 400 500))) '(1 200 300 400 500)
procedure
(stream-ref-lens i) → lens?
i : exact-nonnegative-integer?
> (lens-view (stream-ref-lens 2) (stream 1 2 3 4 5 6)) 3
> (stream->list (lens-set (stream-ref-lens 2) (stream 1 2 3 4 5 6) 'a)) '(1 2 a 4 5 6)
2.2.7 Dict lenses
(require lens/data/dict) | package: lens-data |
The Lens Guide has additional examples of dictionary lenses.
procedure
(dict-ref-lens key) → lens?
key : any/c
> (define dict '((a . 1) (b . 2) (c . 3))) > (lens-view (dict-ref-lens 'a) dict) 1
> (lens-set (dict-ref-lens 'a) dict 100) '((a . 100) (b . 2) (c . 3))
2.3 Applicable lenses
(require lens/applicable) | package: lens-lib |
This module provides the same functions as lens, but enables the use of applicable lenses. Applicable lenses may be used directly as getter functions, removing the need to use lens-view.
> (require lens/applicable) > (first-lens '(a b c)) 'a
> (map first-lens '((1 2 3) (a b c) (100 200 300))) '(1 a 100)
Attempting to use non-applicable lenses as functions is an error.
> (require lens) > (first-lens '(a b c)) cannot apply a non-applicable lens as a function