On this page:
1.2.1.1 Pairs and Lists
1.2.1.1.1 Fetching multiple list values at once
1.2.1.2 Vectors and Strings
1.2.1.3 Streams
6.12
1.2.1 Lenses on Ordered Data

Many Racket data structures hold ordered or sequential values. Lenses for accessing elements of these structures by index are provided.

1.2.1.1 Pairs and Lists

The Lens Reference has additional information on pair and list lenses.

The two primitive pair lenses are car-lens and cdr-lens:

> (lens-transform car-lens '(1 . 2) (curry * 2))

'(2 . 2)

> (lens-transform cdr-lens '(1 . 2) (curry * 2))

'(1 . 4)

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 list-ref-lens lens constructor, which produces a new lens given an index to look up. Abbreviation lenses such as first-lens and second-lens are provided for common use-cases:

> (lens-transform (list-ref-lens 3) (range 10) sub1)

'(0 1 2 2 4 5 6 7 8 9)

> (lens-transform third-lens (range 10) sub1)

'(0 1 1 3 4 5 6 7 8 9)

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 list-ref-lens:

> (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)

'((1 2 3) (4 5 6) (7 0 9))

This can also be generalized to n-dimensional lists:

> (define (list-ref-lens* . indices)
    (apply lens-compose (map list-ref-lens indices)))
> (lens-set (list-ref-lens* 0 1 0)
            '(((a b) (c d))
              ((e f) (g h)))
            'z)

'(((a b) (z d)) ((e f) (g h)))

This function is actually provided by lens under the name list-ref-nested-lens, but the above example demonstrates that it’s really a derived concept.

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

> (define first-two-lens (lens-join/list first-lens second-lens))
> (lens-view first-two-lens '(1 2 3 4))

'(1 2)

> (lens-set first-two-lens '(1 2 3 4) '(a b))

'(a b 3 4)

> (lens-transform first-two-lens '(1 2 3 4) (curry map sub1))

'(0 1 3 4)

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.

1.2.1.2 Vectors and Strings

The Vector lenses and String Lenses 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 vector-ref-lens and string-ref-lens lens constructors produce random-access lenses, and lens-join/vector and lens-join/string combine multiple lenses with vector or string targets.

> (lens-transform (vector-ref-lens 1) #("a" "b" "c") string->symbol)

'#("a" b "c")

> (lens-transform (string-ref-lens 3) "Hello!" char-upcase)

"HelLo!"

1.2.1.3 Streams

The Lens Reference has additional information on stream lenses.

Racket’s streams contain ordered data, much like lists, but unlike lists, they are 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-view (stream-ref-lens 10)
             (stream-map (curry expt 2) (in-naturals)))

1024

Keep in mind that since 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.