[funcool/lentes "1.2.0"]
This is an implementation of functional references modeled as functions (in the same way as transducers). Is a generalization of get and put mapping to the particular part of a data structure.
Please, see Quick Start section in order to understand it better seeing some example code.
This documentation does not covers all api, so if you miss some function, contributions are very welcome. You can see the full API documentation here. |
Since lentes is a young project there can be some API breakage.
The simplest way to use lentes in a clojure project, is by including it in the dependency vector on your project.clj file:
[funcool/lentes "1.2.0"]
Do not be scared with the package name and its description. It is pretty strighforward to use and understand. The lense consists in a couple of functions responsibles for transform data structures in both directions.
Let’s create a couple of functions for getter and setter:
(defn my-getter
[state]
(:foo state))
(defn my-setter
[state f]
(update state :foo f))
Now, you can use that functions for access or modify data:
(def data {:foo 1 :bar 2})
(my-getter data)
;; => 1
(my-setter data inc)
;; => {:foo 2 :bar 2}
Now, we can generalize that using lenses abstraction:
(require '[lentes.core :as l])
(def mylens (l/lens my-getter my-setter))
(l/focus mylens state)
;; => 1
(l/over mylens inc state)
;; => {:foo 2 :bar 2}
This all code can be awoided just using a one of the great bunch of helpers that comes with lentes library out of the box (explained later):
(def mylens (l/key :foo))
(l/focus mylens state)
;; => 1
(l/over mylens inc state)
;; => {:foo 2 :bar 2}
But, the best part is coming where you are using atoms for store your state and want
to create other (derived atoms) that acts like a limited version of the original
atom, also called materialized view. That you can do with l/derive
function:
(def state1 (atom {:foo 1 :bar 1}))
(def state2 (l/derive (l/key :foo) state1))
(satisfies? IAtom state2)
;; => true
@state2
;; => 1
(swap! state2 inc)
@state2
;; => 2
@state1
;; => {:foo 2 :bar 2}
The derived atoms has very useful properties such as they are lazy (no code executed until is really needed) and smart (does not trigger watches if the focused data is not affected).
This is specially useful, when you want to create a materialized views of the global state and use them together with rum facilities for react on atom changes, allowing components rerender only when the affected state is changed.
This section tries to explain the basic api of lenses and its composition that later can be used for derive atoms from other atoms.
We’ll start by using the most basic lens, the identity lens. We can get the value
a lens is focused on using the focus
function in lentes.core
ns:
(require '[lentes.core :as l])
(l/focus l/id [0 1 2 3])
;; => [0 1 2 3]
We have two other primitives available: the put
operation and over
, which lets
us apply a function over the focused value of a lens:
(l/put l/id 42 [0 1 2 3])
;; => 42
(l/over l/id count [0 1 2 3])
;; => 4
There are some builtin lenses that can be used out of the box with sequences such as
fst
, snd
and nth
. Let see some examples on how them can be used:
fst
lense;; Focus over the first element of a vector
(l/focus l/fst [1 2 3])
;; => 1
;; Apply a function over first element of a vector
(l/over l/fst inc [1 2 3])
;; => [2 2 3]
;; Replace the first value of an element of a vector
(l/put l/fst 42 [1 2 3])
;; => [42 2 3]
nth
lense(l/focus (l/nth 2) [1 2 3])
;; => 3
(l/over (l/nth 2) inc [1 2 3])
;; => [1 2 4]
(l/put (l/nth 2) 42 [1 2 3])
;; => [1 2 42]
There’s key
and select-keys
for focusing on one or multiple keys respectively:
(l/focus (l/key :a) {:a 1 :b 2})
;; => 1
(l/over (l/key :a) str {:a 1 :b 2})
;; => {:a "1", :b 2}
(l/put (l/key :a) 42 {:a 1 :b 2})
;; => {:a 42, :b 2}
(l/focus (l/select-keys [:a]) {:a 1 :b 2})
;; => {:a 1}
(l/over (l/select-keys [:a :c])
(fn [m]
(zipmap (keys m) (repeat 42)))
{:a 1 :b 2})
;; => {:b 2, :a 42}
(l/put (l/select-keys [:a :c])
{:a 0}
{:a 1 :b 2 :c 42})
;; => {:b 2, :a 0}
in
for focusing on a path:
(l/focus (l/in [:a :b])
{:a {:b {:c 42}}})
;; => {:c 42}
(l/over (l/in [:a :b]) #(zipmap (vals %) (keys %))
{:a {:b {:c 42}}})
;; => {:a {:b {42 :c}}}
(l/put (l/in [:a :b])
42
{:a {:b {:c 42}}})
;; => {:a {:b 42}}
Let’s take a look at a combinator that will let us build a unit-conversion lens
called units
. We have to supply a function to convert from unit a
to unit b
and viceversa:
(defn sec->min [sec] (/ sec 60))
(defn min->sec [min] (* min 60))
(def mins (l/units sec->min
min->sec))
(l/focus mins 120)
;; => 2
(l/put mins 3 120)
;; => 180
(l/over mins inc 60)
;; => 120
And conditional lenses that can be defined using a predicate function and returns a lens that focuses in an element only if it passes the predicate:
(l/focus (l/passes even?) 2)
;; => 2
(l/over (l/passes even?) inc 2)
;; => 3
(l/put (l/passes even?) 42 2)
;; => 42
(l/focus (l/passes even?) 1)
;; => nil
(l/over (l/passes even?) inc 1)
;; => 1
(l/put (l/passes even?) 42 1)
;; => 1
One of the big advantatges of this lenses implementation is because it is implemented in termps of function composition, much in the same line as transducers. Let see a example:
(def my-lens (comp l/fst l/fst (l/nth 2)))
(def data
[[0 1 2]
[3 4 5]])
(l/focus my-lens data)
;; => 2
(l/put my-lens 42 data)
;; => [[0 1 42] [3 4 5]]
Lenses compose with regular function composition and, like transducers, the combined lens runs from left to right.
Five most important rules:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Readability counts.
All contributions to lentes should keep these important rules in mind.
Please read CONTRIBUTING.md
file on the root of repository.
lentes is open source and can be found on github.
You can clone the public repository with this command:
git clone https://github.com/funcool/lentes
For running tests just execute this:
./scripts/build
node ./out/tests.js
lein test
lentes is licensed under BSD (2-Clause) license:
Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close