Liking cljdoc? Tell your friends :D

lentes - lenses for clojure(script)

Introduction

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.

Project Maturity

Since lentes is a young project there can be some API breakage.

Install

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"]

Quick Start

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.

Introduction to lenses

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}

Working with atoms

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.

Reference

This section tries to explain the basic api of lenses and its composition that later can be used for derive atoms from other atoms.

Builtin lenses

Identity

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

Sequences

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:

Example using 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]
Example using the 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]

Associative data structures

There’s key and select-keys for focusing on one or multiple keys respectively:

Example focusing in a specific key/keys of associative data structure
(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:

Example focusing in neest data structures
(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:

Example definiting a "unit conversion" lense
(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

Conditionals

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:

Example focusing using conditional lenses
(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

Composition

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.

Developers Guide

Philosophy

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.

Contributing

Please read CONTRIBUTING.md file on the root of repository.

Source Code

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

Run tests

For running tests just execute this:

Run tests on node platform
./scripts/build
node ./out/tests.js
Run tests on jvm platform
lein test

License

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