Introduction

A lens is a value that composes a getter and a setter function 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 data. -- Racket "lens" documentation

Lenses

Lenses are "functional pointers", getters and setters combined accessing some larger structure. Lenses can be converted into getters using the view function, as setters sett, as "updaters" using over.

vanilla Clojure lens
(nth [0 1] 0) (view (nx 0) [0 1])
(assoc [0 1] 0 99) (sett (nx 0) 99 [0 1])
(update [0 1] 0 inc) (over (nx 0) inc [0 1])

While Clojure's vanilla functions are succinct and sufficient in simple cases, lens are much more powerful and composable as the data complexity grows.

ix k

ix k uses k as a key to retrieve and update any indexed data.

[
    (over (ix :a) inc {:a 100 :b 99})
    (sett (ix 0) 42 [-99 100])
]

_1 _2 _3 _4 _5

_1 is simply (ix 0), _2 simply (ix 1), and so on, providing shorthands for accessing common elements.

nx k

[
    (sett (nx 0) 42 [-99 100])
]

ix variant, where the key is an integer, and uses nth under the hood. This lens throws error upon accessing out of index elements.

*>

Function composition, Haskell style, used in composing lenses.

One of the great properties of lenses is that they compose just under the vanilla function composition, enabling Haskell programmers to compose lenses like player.pos.x.

In Haskell, function composition, such as (f . g) a b is evaluated to be (f g a) b, where Clojure's ((comp f g) a b) is (f (g a b)), rendering it unusable for lens composition.

*> mimics Haskell's function composition, so that ((*> f g) a b) is ((f g a) b), usable for lens composition.

(over (*> (ix 0) (ix 1)) inc [[0 1] [2 3]])

If you pass integers and keywords to *>, they will automatically be converted into lens using ix.

(over (*> 0 1 :foo) inc [[0 {:foo 42}] [2 3]])

*> somewhat resembles vanilla Clojure's sequence of keys used in update-in and assoc-in.

(->> {:pos [0 0]}
  (over (*> :pos 0) inc)
  (over (*> :pos 1) dec))

*> is also called hcomp.

lens

lens constructs a lens from a getter and setter. The result might not be the most efficient, but it is certainlly the most familiar method to construct lenses.

(let [l (lens first
              (fn [coll e] (assoc coll 0 e)))]
    [
        (view l [99 98 97 96 95])
        (over l inc [99 98 97 96 95])
    ])

to

to converts a getter into a "lens" that can only get.

(view (to first) [3 5 7 9])

join l1 l2 ...

Constructs a lens, with the view of the original lenses combined into a vector.

(def _13 (join (nx 0) (nx 2)))

(let [v [:a :b :c :d :e :f]]
    [
      (view _13 v)
      (over _13 (fn [[x y]] [y x]) v)
      (sett _13 [42 99] v)
    ])

You could easily construct degenerate lenses if the original views of the lenses overlap.

Maths Operators

(->> {:pos [0 0]}
  (+= (*> :pos 0) 1)
  (-= (*> :pos 1) 2))

Shorthands, (+= l n s) is (over l (partial + n) s).

Auto Lifting

[
    (view 0 [1 2 3 4 5])
    (over 0 inc [1 2 3 4 5])
    (sett :hello :world {:hello :earth})
]

view, sett, and over (and some other functions) implicitly converts keywords and integers to lenses using ix.

Traversals

Traversals are like lens, but access multiple values. Thus they cannot be simply be converted to getters using view. to-list-of will retrieve all the values.

over and sett can still operate on traversals.

each

Access all values in a collection.

[
    (sett each 100 [[:c :d] [:b :c] [:a] [:m]])
    (to-list-of each [[:c :d] [:b :c] [:a] [:m]])
    (sett (*> each each) 100 [[:c :d] [:b :c] [:a] [:m]])
    (over (*> each each) inc [[1 2 3] [4 5 6] [7 8 9 10] []])
    (to-list-of (*> each each) [[1 2 3] [4 5 6] [7 8 9 10] []])
]

_ranging, _taking, _dropping

These traversals access all elements, but only operates on values whose indexes match their predicates.

[
    (range 0 30 4)
    (to-list-of (_ranging (range 3 5)) (range 0 30 4))
    (to-list-of (_taking 5) (range 0 30 4))
    (over (_dropping 5) dec (range 0 30 4))
]

_index

Generalized range based traversal, takes a custom predicate that traverses all elements, and only operates on indexes that satisfies the predicate.

[
    (sett (_index even?) 42 (range 0 5))
    (sett (_index odd?) 42 (range 0 5))
]

_filtering, filtered pred

_filtering accesses all elements, and only operates on values that satisfy the predicate.

(to-list-of (_filtering even?) [1 1 2 3 5 8])

filtered only operates on values that satisfy the predicate.

[
    (to-list-of (*> each (filtered even?)) [1 1 2 3 5 8])
    (to-list-of (filtered even?) 2)
]

"state" macros, pseudo-imperative constructs

Haskell's lens library provides operators and constructs dealing with the state monad.

State monad can be unidiomatic in Clojure and the need for the state monad is to some extent mitigated via the threading macros, -> and ->>, which enables us writing code reminiscent of imperative programming.

(->> {:pos [0 0]}
     (+= (*> :pos 0) 1)
     (-= (*> :pos 1) 2))

This library provides additional macros to facilitate this pseudo-imperative style.

The following macros can be found in the coarse.macros namespace.

+>>, the zooming operator

+>> "zooms" into a substructure using a lens, reminiscent of the original library's zoom function.

(def game-state
  {:player {:pos [0 0]}})

(->> game-state
     (+>> (*> :player :pos)
        (+= (nx 0) 1)
        (-= (nx 1) 2)))
; {:player {:pos [1 -2]}}

using

using is a variant of let that binds results retrieved via lenses to local variables.

; swaps the two elements of the position
(->> {:player {:pos [-3 7]}}
     (+>> (*> :player :pos)
        (using [x _1
                y _2]
            (=% _1 y)
            (=% _2 x))))
; {:player {:pos [7 -3]}}

results matching ""

    No results matching ""