├── scripts
├── build
├── watch
├── repl
├── repl.clj
├── watch.clj
└── build.clj
├── doc
├── cljdoc.edn
├── content-docinfo.html
├── Makefile
└── content.adoc
├── mvn-upload.sh
├── .gitignore
├── README.md
├── .travis.yml
├── deps.edn
├── CONTRIBUTING.md
├── LICENSE
├── pom.xml
├── CHANGES.md
├── tools.clj
├── test
└── lentes
│ └── tests.cljc
└── src
└── lentes
└── core.cljc
/scripts/build:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | lein trampoline run -m clojure.main scripts/build.clj
3 |
--------------------------------------------------------------------------------
/scripts/watch:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | lein trampoline run -m clojure.main scripts/watch.clj
3 |
--------------------------------------------------------------------------------
/scripts/repl:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | rlwrap lein trampoline run -m clojure.main scripts/repl.clj
3 |
--------------------------------------------------------------------------------
/doc/cljdoc.edn:
--------------------------------------------------------------------------------
1 | {:cljdoc.doc/tree [["User Guide" {:file "doc/content.adoc"}]
2 | ["Changelog" {:file "CHANGES.md"}]]}
3 |
--------------------------------------------------------------------------------
/mvn-upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | mvn deploy:deploy-file -Dfile=target/lentes.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/
3 |
--------------------------------------------------------------------------------
/scripts/repl.clj:
--------------------------------------------------------------------------------
1 | (require
2 | '[cljs.repl :as repl]
3 | '[cljs.repl.nashorn :as nashorn]
4 | '[cljs.repl.node :as node])
5 |
6 | (cljs.repl/repl
7 | (cljs.repl.node/repl-env)
8 | :output-dir "out"
9 | :cache-analysis true)
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml.asc
5 | *.jar
6 | *.class
7 | /.lein-*
8 | /.nrepl-port
9 | /*-init.clj
10 | /doc/dist
11 | /out
12 | /repl
13 | /node_modules
14 | /nashorn_code_cache
15 | /.cpcache
16 | /.rebel_readline_history
--------------------------------------------------------------------------------
/doc/content-docinfo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | all: doc
2 |
3 | api:
4 | cd ..
5 | lein doc
6 |
7 | doc:
8 | mkdir -p dist/latest/
9 | asciidoctor -a docinfo -a stylesheet! -o dist/latest/index.html content.adoc
10 |
11 | github: api doc
12 | ghp-import -m "Generate documentation" -b gh-pages dist/
13 | git push origin gh-pages
14 |
--------------------------------------------------------------------------------
/scripts/watch.clj:
--------------------------------------------------------------------------------
1 | (require '[cljs.build.api :as b])
2 |
3 | (b/watch
4 | (b/inputs "test" "src")
5 | {:main 'lentes.tests
6 | :parallel-build false
7 | :output-to "out/tests.js"
8 | :output-dir "out/tests"
9 | :target :nodejs
10 | :optimizations :none
11 | :pretty-print true
12 | :language-in :ecmascript6
13 | :language-out :ecmascript5
14 | :verbose true})
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lentes - functional references for clojure(script) #
2 |
3 | [](https://clojars.org/funcool/lentes)
4 |
5 | [](https://clojars.org/funcool/lentes)
6 |
7 | **Documentation**: https://cljdoc.org/d/funcool/lentes/1.4.0-SNAPSHOT
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: clojure
2 | sudo: false
3 | lein: lein
4 |
5 | script:
6 | - ./scripts/build
7 | - nvm install v6.2.2
8 | - node --version
9 | - node out/tests.js
10 |
11 | branches:
12 | only:
13 | - master
14 |
15 | jdk:
16 | - oraclejdk8
17 |
18 | notifications:
19 | email:
20 | recipients:
21 | - niwi@niwi.nz
22 | on_success: change
23 | on_failure: change
24 |
--------------------------------------------------------------------------------
/scripts/build.clj:
--------------------------------------------------------------------------------
1 | (require '[cljs.build.api :as b])
2 |
3 | (println "Building ...")
4 |
5 | (let [start (System/nanoTime)]
6 | (b/build
7 | (b/inputs "test" "src")
8 | {:main 'lentes.tests
9 | :parallel-build false
10 | :output-to "out/tests.js"
11 | :output-dir "out/tests"
12 | :target :nodejs
13 | :optimizations :none
14 | :pretty-print true
15 | :language-in :ecmascript6
16 | :language-out :ecmascript5
17 | :verbose true})
18 | (println "... done. Elapsed" (/ (- (System/nanoTime) start) 1e9) "seconds"))
19 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {}
2 | :paths ["src"]
3 | :aliases
4 | {:dev
5 | {:extra-paths ["test" "target"]
6 | :extra-deps
7 | {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
8 | com.bhauman/rebel-readline {:mvn/version "0.1.4"}
9 | org.clojure/clojurescript {:mvn/version "1.10.520"}
10 | org.clojure/clojure {:mvn/version "1.10.1"}
11 | org.clojure/test.check {:mvn/version "0.9.0"}}}
12 | :repl
13 | {:main-opts ["-m" "rebel-readline.main"]}
14 | :ancient
15 | {:main-opts ["-m" "deps-ancient.deps-ancient"]
16 | :extra-deps {deps-ancient {:mvn/version "RELEASE"}}}
17 | :jar
18 | {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}}
19 | :main-opts ["-m" "hf.depstar.jar" "target/lentes.jar"]}}}
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributed Code #
2 |
3 | In order to keep *lentes* completely free and unencumbered by copyright, all new
4 | contributors to the *lentes* code base are asked to dedicate their contributions to
5 | the public domain. If you want to send a patch or enhancement for possible inclusion
6 | in the *lentes* source tree, please accompany the patch with the following
7 | statement:
8 |
9 | The author or authors of this code dedicate any and all copyright interest
10 | in this code to the public domain. We make this dedication for the benefit of
11 | the public at large and to the detriment of our heirs and successors. We
12 | intend this dedication to be an overt act of relinquishment in perpetuity of
13 | all present and future rights to this code under copyright law.
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2019 Andrey Antukh
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | funcool
5 | lentes
6 | jar
7 | 1.4.0-SNAPSHOT
8 | lentes
9 | Functional references for Clojure and ClojureScript
10 | https://github.com/funcool/lentes
11 |
12 |
13 | BSD (2-Clause)
14 | http://opensource.org/licenses/BSD-2-Clause
15 |
16 |
17 |
18 | scm:git:git://github.com/funcool/lentes.git
19 | scm:git:ssh://git@github.com/funcool/lentes.git
20 | master
21 | https://github.com/funcool/lentes
22 |
23 |
24 | src
25 |
26 |
27 |
28 | clojars
29 | https://repo.clojars.org/
30 |
31 |
32 |
33 |
34 | org.clojure
35 | clojure
36 | 1.10.1
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # Changelog #
2 |
3 | ## Version 1.4.0-SNAPSHOT
4 |
5 | Date: 2024-09-03
6 |
7 | - Improved docs (thank you @blak3mill3r, @ComedyTomedy, @luposlip, and @rgkirch).
8 |
9 | Date: 2020-01-07
10 |
11 | - Fix internal cache race conditions.
12 | - Add Atom2 impl (thanks to @jellelicht).
13 |
14 |
15 | ## Version 1.3.3 ##
16 |
17 | Date: 2019-10-22
18 |
19 | - Fix jvm impl (bug introduced in previous commits).
20 |
21 |
22 | ## Version 1.3.2 ##
23 |
24 | Date: 2019-10-22
25 |
26 | Same as 1.3.1 (just docs updates).
27 |
28 |
29 | ## Version 1.3.1 ##
30 |
31 | Date: 2019-10-22
32 |
33 | Same as 1.3.0 (just docs updates).
34 |
35 |
36 | ## Version 1.3.0 ##
37 |
38 | Date: 2019-10-22
39 |
40 | - Improve equality checking on derived atoms.
41 | - Minor code/performance improvements.
42 |
43 |
44 | ## Version 1.2.0 ##
45 |
46 | Date: 2016-09-30
47 |
48 | - Change license to BSD 2-Clause
49 | - Remove the deprecated `getter` function.
50 | - Fix `select-keys` lense in cljs impl.
51 | - Improve derived atom perfromance.
52 | Adding cache and proxying the watchers in order to reduce
53 | N watchers to derived atom to 1 watcher to the source atom.
54 |
55 |
56 | ## Version 1.1.0 ##
57 |
58 | Date: 2016-06-22
59 |
60 | - Improve documentation.
61 | - Add missing IAtom marker for derived atom (cljs only)
62 | - Add the ability to create read only derived refs.
63 | - Add `derive` function as substitute to `focus-atom` (backward compatible).
64 | - Add arity 1 for `lens` function that allows create read only lenses.
65 | - Add the ability to disable equality check on derived atoms.
66 | - Deprecate `getter` in favor of `lens`.
67 |
68 |
69 | ## Version 1.0.1 ##
70 |
71 | Date: 2016-03-20
72 |
73 | - Minor code style fixes.
74 | - Add getter lense shortcut.
75 |
76 |
77 | ## Version 1.0.0 ##
78 |
79 | Date: 2016-02-26
80 |
81 | Initial version (split from cats 1.2.1).
82 |
--------------------------------------------------------------------------------
/tools.clj:
--------------------------------------------------------------------------------
1 | (require '[clojure.java.shell :as shell])
2 |
3 | (require '[cljs.build.api :as api]
4 | '[cljs.repl :as repl]
5 | '[cljs.repl.node :as node])
6 |
7 | (require '[rebel-readline.core]
8 | '[rebel-readline.clojure.main]
9 | '[rebel-readline.clojure.line-reader]
10 | '[rebel-readline.clojure.service.local]
11 | '[rebel-readline.cljs.service.local]
12 | '[rebel-readline.cljs.repl])
13 |
14 | (defmulti task first)
15 |
16 | (defmethod task :default
17 | [args]
18 | (let [all-tasks (-> task methods (dissoc :default) keys sort)
19 | interposed (->> all-tasks (interpose ", ") (apply str))]
20 | (println "Unknown or missing task. Choose one of:" interposed)
21 | (System/exit 1)))
22 |
23 | (defmethod task "repl:jvm"
24 | [args]
25 | (rebel-readline.core/with-line-reader
26 | (rebel-readline.clojure.line-reader/create
27 | (rebel-readline.clojure.service.local/create))
28 | (clojure.main/repl
29 | :prompt (fn []) ;; prompt is handled by line-reader
30 | :read (rebel-readline.clojure.main/create-repl-read))))
31 |
32 | (defmethod task "repl:node"
33 | [args]
34 | (rebel-readline.core/with-line-reader
35 | (rebel-readline.clojure.line-reader/create
36 | (rebel-readline.cljs.service.local/create))
37 | (cljs.repl/repl
38 | (node/repl-env)
39 | :prompt (fn []) ;; prompt is handled by line-reader
40 | :read (rebel-readline.cljs.repl/create-repl-read)
41 | :output-dir "out"
42 | :cache-analysis false)))
43 |
44 | (def build-options
45 | {:main 'lentes.tests
46 | :output-to "target/tests.js"
47 | :source-map "target/tests.js.map"
48 | :output-dir "target/tests"
49 | :optimizations :advanced
50 | :target :nodejs
51 | :pretty-print false
52 | :pseudo-names false
53 | :verbose true})
54 |
55 | (defmethod task "build:tests"
56 | [args]
57 | (api/build (api/inputs "src" "test") build-options))
58 |
59 | (defmethod task "watch:tests"
60 | [args]
61 | (println "Start watch loop...")
62 | (letfn [(run-tests []
63 | (let [{:keys [out err]} (shell/sh "node" "out/tests.js")]
64 | (println out err)))
65 | (start-watch []
66 | (try
67 | (api/watch (api/inputs "src" "test")
68 | (assoc build-options
69 | :watch-fn run-tests
70 | :source-map true
71 | :optimizations :none))
72 | (catch Exception e
73 | (println "ERROR:" e)
74 | (Thread/sleep 2000)
75 | start-watch)))]
76 | (trampoline start-watch)))
77 |
78 | ;;; Build script entrypoint. This should be the last expression.
79 |
80 | (task *command-line-args*)
81 |
--------------------------------------------------------------------------------
/test/lentes/tests.cljc:
--------------------------------------------------------------------------------
1 | (ns lentes.tests
2 | (:refer-clojure :exclude [derive])
3 | #?(:cljs
4 | (:require [cljs.test :as t]
5 | [clojure.test.check]
6 | [clojure.test.check.generators :as gen :include-macros true]
7 | [clojure.test.check.properties :as prop :include-macros true]
8 | [clojure.test.check.clojure-test :refer-macros (defspec)]
9 | [lentes.core :as l])
10 | :clj
11 | (:require [clojure.test :as t]
12 | [clojure.test.check]
13 | [clojure.test.check.clojure-test :refer (defspec)]
14 | [clojure.test.check.generators :as gen]
15 | [clojure.test.check.properties :as prop]
16 | [lentes.core :as l])))
17 |
18 | ;; laws
19 |
20 | (defn first-lens-law
21 | [{:keys [gen lens xgen] :or {xgen gen/any}}]
22 | (prop/for-all
23 | [s gen x xgen]
24 | (t/is (= x (l/focus lens (l/put lens x s))))))
25 |
26 | (defn second-lens-law
27 | [{:keys [gen lens]}]
28 | (prop/for-all
29 | [s gen]
30 | (t/is (= s (l/put lens (l/focus lens s) s)))))
31 |
32 | (defn third-lens-law
33 | [{:keys [gen lens xgen] :or {xgen gen/any}}]
34 | (prop/for-all
35 | [s gen
36 | a xgen
37 | b xgen]
38 | (t/is (= (l/put lens a s)
39 | (l/put lens a (l/put lens b s))))))
40 |
41 | ;; generators
42 |
43 | (def vector-gen
44 | (gen/vector gen/any 10))
45 |
46 | (def nested-vector-gen
47 | (gen/vector vector-gen 10))
48 |
49 | ;; id
50 |
51 | (def id-lens
52 | {:gen gen/any
53 | :lens l/id})
54 |
55 | (defspec id-first-lens-law 10
56 | (first-lens-law id-lens))
57 |
58 | (defspec id-second-lens-law 10
59 | (second-lens-law id-lens))
60 |
61 | (defspec id-third-lens-law 10
62 | (third-lens-law id-lens))
63 |
64 | ;; passes
65 |
66 | (t/deftest passes
67 | (let [odd (l/passes odd?)]
68 | (t/testing "focus"
69 | (t/is (= 3 (l/focus odd 3)))
70 | (t/is (nil? (l/focus odd 2))))
71 | (t/testing "put"
72 | (t/is (= 42 (l/put odd 42 3)))
73 | (t/is (= 2 (l/put odd 42 2))))
74 | (t/testing "over"
75 | (t/is (= 4 (l/over odd inc 3)))
76 | (t/is (= 2 (l/over odd inc 2))))))
77 |
78 | (t/deftest passes-comp
79 | (let [fstodd (comp l/fst (l/passes odd?))]
80 | (t/testing "focus"
81 | (t/is (= 1 (l/focus fstodd [1 2 3])))
82 | (t/is (nil? (l/focus fstodd [2 3 4]))))
83 | (t/testing "put"
84 | (t/is (= [42 2 3] (l/put fstodd 42 [1 2 3])))
85 | (t/is (= [2 3 4] (l/put fstodd 42 [2 3 4]))))
86 | (t/testing "over"
87 | (t/is (= [2 2 3] (l/over fstodd inc [1 2 3])))
88 | (t/is (= [2 3 4] (l/over fstodd inc [2 3 4]))))))
89 |
90 | ;; nth
91 |
92 | (defspec nth-first-lens-law 10
93 | (first-lens-law {:gen vector-gen
94 | :lens (l/nth 0)}))
95 |
96 | (defspec nth-second-lens-law 10
97 | (second-lens-law {:gen vector-gen
98 | :lens (l/nth 0)}))
99 |
100 | (defspec nth-third-lens-law 10
101 | (third-lens-law {:gen vector-gen
102 | :lens (l/nth 0)}))
103 |
104 | ;;; nth composition
105 |
106 | (def ffst (comp l/fst l/fst))
107 |
108 | (def ffst-lens
109 | {:gen nested-vector-gen
110 | :lens ffst})
111 |
112 | (defspec ffst-first-lens-law 10
113 | (first-lens-law ffst-lens))
114 |
115 | (defspec ffst-second-lens-law 10
116 | (second-lens-law ffst-lens))
117 |
118 | (defspec ffst-third-lens-law 10
119 | (third-lens-law ffst-lens))
120 |
121 | ;; tail
122 |
123 | (def tail-lens
124 | {:gen vector-gen
125 | :xgen vector-gen
126 | :lens l/tail})
127 |
128 | (defspec tail-first-lens-law 10
129 | (first-lens-law tail-lens))
130 |
131 | (defspec tail-second-lens-law 10
132 | (second-lens-law tail-lens))
133 |
134 | (defspec tail-third-lens-law 10
135 | (third-lens-law tail-lens))
136 |
137 | ;; associative
138 |
139 | (defn with-key
140 | [k]
141 | (gen/fmap (partial hash-map k) gen/any))
142 |
143 | (def key-lens
144 | {:gen (with-key :foo)
145 | :lens (l/key :foo)})
146 |
147 | (defspec key-first-lens-law 10
148 | (first-lens-law key-lens))
149 |
150 | (defspec key-second-lens-law 10
151 | (second-lens-law key-lens))
152 |
153 | (defspec key-third-lens-law 10
154 | (third-lens-law key-lens))
155 |
156 | ;; select-keys
157 |
158 | (defn with-keys
159 | [ks]
160 | (gen/fmap (fn [v]
161 | (zipmap ks (repeat v)))
162 | gen/any))
163 |
164 | (def select-keys-lens
165 | {:gen (with-keys [:a :b])
166 | :xgen (with-keys [:a :b])
167 | :lens (l/select-keys [:a :b])})
168 |
169 | (defspec select-keys-first-lens-law 10
170 | (first-lens-law select-keys-lens))
171 |
172 | (defspec select-keys-second-lens-law 10
173 | (second-lens-law select-keys-lens))
174 |
175 | (defspec select-keys-third-lens-law 10
176 | (third-lens-law select-keys-lens))
177 |
178 | ;; in
179 |
180 | (def in-lens
181 | {:gen (gen/fmap (fn [m]
182 | (merge m {:a {:b {:c 42}}}))
183 | (gen/map gen/keyword gen/any))
184 | :lens (l/in [:a :b :c])})
185 |
186 | (defspec in-first-lens-law 10
187 | (first-lens-law in-lens))
188 |
189 | (defspec in-second-lens-law 10
190 | (second-lens-law in-lens))
191 |
192 | (defspec in-third-lens-law 10
193 | (third-lens-law in-lens))
194 |
195 | ;; derived lenses
196 |
197 | (defn sec->min
198 | [sec]
199 | (/ sec 60))
200 |
201 | (defn min->sec
202 | [min]
203 | (* min 60))
204 |
205 | (def min-lens
206 | {:gen gen/int
207 | :xgen gen/int
208 | :lens (l/units sec->min min->sec)})
209 |
210 | (defspec min-first-lens-law 10
211 | (first-lens-law min-lens))
212 |
213 | (defspec min-second-lens-law 10
214 | (second-lens-law min-lens))
215 |
216 | (defspec min-third-lens-law 10
217 | (third-lens-law min-lens))
218 |
219 | ;; interop
220 |
221 | (t/deftest derive-rw
222 | (let [source (atom [0 1 2 3 4])
223 | fsource (l/derive l/fst source)]
224 | (t/is (= @fsource 0))
225 |
226 | #?@(:clj [(t/is (instance? clojure.lang.IAtom fsource))
227 | (t/is (instance? clojure.lang.IAtom2 fsource))
228 | (t/is (instance? clojure.lang.IDeref fsource))
229 | (t/is (instance? clojure.lang.IRef fsource))]
230 | :cljs [(t/is (satisfies? IDeref fsource))
231 | (t/is (satisfies? IReset fsource))
232 | (t/is (satisfies? ISwap fsource))
233 | (t/is (satisfies? IWatchable fsource))])
234 |
235 | (swap! source #(subvec % 1))
236 | (t/is (= @source [1 2 3 4]))
237 | (t/is (= @fsource 1))
238 |
239 | (reset! fsource 42)
240 | (t/is (= @source [42 2 3 4]))
241 | (t/is (= @fsource 42))))
242 |
243 | (t/deftest derive-ro
244 | (let [source (atom [0 1 2 3 4])
245 | fsource (l/derive l/fst source {:read-only? true})]
246 | (t/is (= @fsource 0))
247 |
248 | #?@(:clj [(t/is (not (instance? clojure.lang.IAtom fsource)))
249 | (t/is (not (instance? clojure.lang.IAtom2 fsource)))
250 | (t/is (instance? clojure.lang.IDeref fsource))
251 | (t/is (instance? clojure.lang.IRef fsource))]
252 | :cljs [(t/is (satisfies? IDeref fsource))
253 | (t/is (not (satisfies? IReset fsource)))
254 | (t/is (not (satisfies? ISwap fsource)))
255 | (t/is (satisfies? IWatchable fsource))])
256 |
257 | (swap! source #(subvec % 1))
258 | (t/is (= @source [1 2 3 4]))
259 | (t/is (= @fsource 1))))
260 |
261 | (t/deftest derive-watches-rw
262 | (let [source (atom [0 1 2 3 4])
263 | watched (volatile! nil)
264 | fsource (l/derive l/fst source)]
265 | (add-watch fsource :test (fn [key ref old new]
266 | (vreset! watched [ref old new])))
267 |
268 | (swap! source #(subvec % 1))
269 | (t/is (= @watched
270 | [fsource 0 1]))
271 |
272 | (swap! fsource inc)
273 | (t/is (= @watched
274 | [fsource 1 2]))
275 |
276 | (remove-watch fsource :test)))
277 |
278 | (t/deftest derive-watches-ro
279 | (let [source (atom [0 1 2 3 4])
280 | watched (volatile! nil)
281 | fsource (l/derive l/fst source {:read-only? true})]
282 | (add-watch fsource :test (fn [key ref old new]
283 | (vreset! watched [ref old new])))
284 |
285 | (swap! source #(subvec % 1))
286 | (t/is (= @watched
287 | [fsource 0 1]))
288 |
289 | (remove-watch fsource :test)))
290 |
291 | #?(:cljs
292 | (do
293 | (enable-console-print!)
294 | (set! *main-cli-fn* #(t/run-tests)))
295 | :clj
296 | (defn -main
297 | [& args]
298 | (let [{:keys [fail]} (t/run-all-tests #"^lentes.tests.*")]
299 | (if (pos? fail)
300 | (System/exit fail)
301 | (System/exit 0)))))
302 |
303 | #?(:cljs
304 | (defmethod t/report [:cljs.test/default :end-run-tests]
305 | [m]
306 | (if (t/successful? m)
307 | (set! (.-exitCode js/process) 0)
308 | (set! (.-exitCode js/process) 1))))
309 |
--------------------------------------------------------------------------------
/doc/content.adoc:
--------------------------------------------------------------------------------
1 | = lentes - lenses for clojure(script)
2 | Andrey Antukh,
3 | :toc: left
4 | :!numbered:
5 | :idseparator: -
6 | :idprefix:
7 | :source-highlighter: pygments
8 | :pygments-style: friendly
9 | :sectlinks:
10 |
11 |
12 | == Introduction
13 |
14 | Lentes is an implementation of functional references modeled as
15 | functions (in the same way as transducers). Lenses are a
16 | generalization of get and put mapping to a particular part of a data
17 | structure.
18 |
19 | Please see the <> for an overview of what
20 | lentes has to offer.
21 |
22 |
23 | === Project Maturity
24 |
25 | Since _lentes_ is a young project there can be some API breakage.
26 |
27 |
28 | === Install
29 |
30 | The simplest way to use _lentes_ in a clojure project, is by including it in the
31 | dependency vector on your *_project.clj_* file:
32 |
33 | [source, clojure]
34 | ----
35 | [funcool/lentes "1.3.3"]
36 | ----
37 |
38 |
39 | [[quick-start]]
40 | == Quick Start
41 |
42 | Do not be scared with the package name and its description. It is pretty
43 | straightforward to use and understand. A lens consists out of couple of
44 | functions responsible for reading and altering nested data structures.
45 |
46 |
47 | === Introduction to lenses
48 |
49 | Let's create a getter and setter function:
50 |
51 | [source, clojure]
52 | ----
53 | (defn my-getter
54 | [state]
55 | (:foo state))
56 |
57 | (defn my-setter
58 | [state f]
59 | (update state :foo f))
60 | ----
61 |
62 | These can be used to access or modify data:
63 |
64 | [source, clojure]
65 | ----
66 | (def data {:foo 1 :bar 2})
67 |
68 | (my-getter data)
69 | ;; => 1
70 |
71 | (my-setter data inc)
72 | ;; => {:foo 2 :bar 2}
73 | ----
74 |
75 | We can generalize the getting and setting using *lenses abstraction*:
76 |
77 | [source, clojure]
78 | ----
79 | (require '[lentes.core :as l])
80 |
81 | (def mylens (l/lens my-getter my-setter))
82 |
83 | (l/focus mylens data)
84 | ;; => 1
85 |
86 | (l/over mylens inc data)
87 | ;; => {:foo 2 :bar 2}
88 | ----
89 |
90 | Lentes comes with some helpers for creating commonly used lenses. As a
91 | result we don't have to write our own `my-getter` and `my-setter`
92 | functions for common use-cases. Keep reading to find out what helpers
93 | are provided with lentes.
94 |
95 | [source, clojure]
96 | ----
97 | (def mylens (l/key :foo))
98 |
99 | (l/focus mylens data)
100 | ;; => 1
101 |
102 | (l/over mylens inc data)
103 | ;; => {:foo 2 :bar 2}
104 | ----
105 |
106 |
107 | === Working with atoms
108 |
109 | Lentes succinctly interfaces with atoms. The `l/derive` function
110 | allows you to create derived atoms that act like limited versions of
111 | the original atom. This technique is also named `materialized views`.
112 |
113 | [source, clojure]
114 | ----
115 | (def state1 (atom {:foo 1 :bar 1}))
116 | (def state2 (l/derive (l/key :foo) state1))
117 |
118 | (satisfies? IAtom state2)
119 | ;; => true
120 |
121 | @state2
122 | ;; => 1
123 |
124 | (swap! state2 inc)
125 | @state2
126 | ;; => 2
127 |
128 | @state1
129 | ;; => {:foo 2 :bar 2}
130 | ----
131 |
132 | The derived atoms has very useful properties such as they are *lazy*
133 | (no code executed until is really needed) and *smart* (does not
134 | trigger watches if the focused data is not affected).
135 |
136 | This is especially useful, when you want to create materialized views
137 | of the global state, and use them together with
138 | link:https://github.com/tonsky/rum[rum] facilities to react on atom
139 | changes, allowing components to re-render only when the affected state
140 | is changed.
141 |
142 |
143 | == Reference
144 |
145 | Lentes offers functions to easily define composable lenses.
146 |
147 | Just as important are a handful of functions that take a lens and
148 | data. When called they return either the value or modified data.
149 |
150 | The examples below cover basic usage of lentes' lenses. It also
151 | demonstrates what the `focus`, `put` and `over` functions do.
152 |
153 |
154 | === Builtin lenses
155 |
156 | ==== Identity
157 |
158 | The most basic lens is the `identity` lens. It allows you to get and
159 | set the data as is.
160 |
161 | The `focus` function allows you to get the value the lens is "focused"
162 | on.
163 |
164 | In this example we create an identity lens. We then call the focus
165 | function with the lens and a vector as arguments. It doesn't get any
166 | simpler then this.
167 |
168 | [source, clojure]
169 | ----
170 | (require '[lentes.core :as l])
171 |
172 | (l/focus l/id [0 1 2 3])
173 | ;; => [0 1 2 3]
174 | ----
175 |
176 | As you can see `focus` just returned the data as is.
177 |
178 | We have two other core functions:
179 |
180 | - `put` allows us to set a value a lens is focusing on.
181 |
182 | - `over` lets us apply a function over the focused value of a lens.
183 |
184 | [source, clojure]
185 | ----
186 | (l/put l/id 42 [0 1 2 3])
187 | ;; => 42
188 |
189 | (l/over l/id count [0 1 2 3])
190 | ;; => 4
191 | ----
192 |
193 | We have only mentioned the `id` lens. Lentes provides more lens helpers. It's
194 | also possible to create your own lenses for your specific needs.
195 |
196 | ==== Sequences
197 |
198 | There are some builtin lenses that work on sequences. These are the `fst`,
199 | `snd` and `nth` lens:
200 |
201 | .Example using `fst` lens
202 | [source, clojure]
203 | ----
204 | ;; Focus over the first element of a vector
205 | (l/focus l/fst [1 2 3])
206 | ;; => 1
207 |
208 | ;; Apply a function over first element of a vector
209 | (l/over l/fst inc [1 2 3])
210 | ;; => [2 2 3]
211 |
212 | ;; Replace the first value of an element of a vector
213 | (l/put l/fst 42 [1 2 3])
214 | ;; => [42 2 3]
215 | ----
216 |
217 | .Example using the `nth` lens
218 | [source, clojure]
219 | ----
220 | (l/focus (l/nth 2) [1 2 3])
221 | ;; => 3
222 |
223 | (l/over (l/nth 2) inc [1 2 3])
224 | ;; => [1 2 4]
225 |
226 | (l/put (l/nth 2) 42 [1 2 3])
227 | ;; => [1 2 42]
228 | ----
229 |
230 |
231 | ==== Associative data structures
232 |
233 | There's `key` and `select-keys` for focusing on one or multiple keys respectively:
234 |
235 | .Example focusing in a specific key/keys of associative data structure
236 | [source, clojure]
237 | ----
238 | (l/focus (l/key :a) {:a 1 :b 2})
239 | ;; => 1
240 |
241 | (l/over (l/key :a) str {:a 1 :b 2})
242 | ;; => {:a "1", :b 2}
243 |
244 | (l/put (l/key :a) 42 {:a 1 :b 2})
245 | ;; => {:a 42, :b 2}
246 |
247 | (l/focus (l/select-keys [:a]) {:a 1 :b 2})
248 | ;; => {:a 1}
249 |
250 | (l/over (l/select-keys [:a :c])
251 | (fn [m]
252 | (zipmap (keys m) (repeat 42)))
253 | {:a 1 :b 2})
254 | ;; => {:b 2, :a 42}
255 |
256 | (l/put (l/select-keys [:a :c])
257 | {:a 0}
258 | {:a 1 :b 2 :c 42})
259 | ;; => {:b 2, :a 0}
260 | ----
261 |
262 | `in` for focusing on a path:
263 |
264 | .Example focusing in nested data structures
265 | [source, clojure]
266 | ----
267 | (l/focus (l/in [:a :b])
268 | {:a {:b {:c 42}}})
269 | ;; => {:c 42}
270 |
271 | (l/over (l/in [:a :b]) #(zipmap (vals %) (keys %))
272 | {:a {:b {:c 42}}})
273 | ;; => {:a {:b {42 :c}}}
274 |
275 | (l/put (l/in [:a :b])
276 | 42
277 | {:a {:b {:c 42}}})
278 | ;; => {:a {:b 42}}
279 | ----
280 |
281 | Let's take a look at a combinator that will let us build a unit-conversion lens
282 | called `units`. We have to supply a function to convert from unit `a` to unit `b`
283 | and viceversa:
284 |
285 | .Example defining a "unit conversion" lens
286 | [source, clojure]
287 | ----
288 | (defn sec->min [sec] (/ sec 60))
289 | (defn min->sec [min] (* min 60))
290 |
291 | (def mins (l/units sec->min
292 | min->sec))
293 |
294 | (l/focus mins 120)
295 | ;; => 2
296 |
297 | (l/put mins 3 120)
298 | ;; => 180
299 |
300 | (l/over mins inc 60)
301 | ;; => 120
302 | ----
303 |
304 |
305 | ==== Conditionals
306 |
307 | Conditional lenses are defined using a predicate function as argument.
308 | It only focuses on the value when the called predicate returns true.
309 | The predicate is called with the value as argument.
310 |
311 | .Example focusing using conditional lenses
312 | [source, clojure]
313 | ----
314 | (l/focus (l/passes even?) 2)
315 | ;; => 2
316 |
317 | (l/over (l/passes even?) inc 2)
318 | ;; => 3
319 |
320 | (l/put (l/passes even?) 42 2)
321 | ;; => 42
322 |
323 | (l/focus (l/passes even?) 1)
324 | ;; => nil
325 |
326 | (l/over (l/passes even?) inc 1)
327 | ;; => 1
328 |
329 | (l/put (l/passes even?) 42 1)
330 | ;; => 1
331 | ----
332 |
333 |
334 | === Composition
335 |
336 | One of the big advantages of this lenses implementation is because it is
337 | implemented in terms of function composition, much in the same line as
338 | transducers. Let see a example:
339 |
340 | [source, clojure]
341 | ----
342 | (def my-lens (comp l/fst (l/nth 2)))
343 |
344 | (def data
345 | [[0 1 2]
346 | [3 4 5]])
347 |
348 | (l/focus my-lens data)
349 | ;; => 2
350 |
351 | (l/put my-lens 42 data)
352 | ;; => [[0 1 42] [3 4 5]]
353 | ----
354 |
355 | Lenses compose with regular function composition and, like transducers, the
356 | combined lens runs from left to right.
357 |
358 |
359 | == Developers Guide
360 |
361 | === Philosophy
362 |
363 | Five most important rules:
364 |
365 | - Beautiful is better than ugly.
366 | - Explicit is better than implicit.
367 | - Simple is better than complex.
368 | - Complex is better than complicated.
369 | - Readability counts.
370 |
371 | All contributions to _lentes_ should keep these important rules in mind.
372 |
373 |
374 | === Contributing
375 |
376 | Please read `CONTRIBUTING.md` file on the root of repository.
377 |
378 |
379 | === Source Code
380 |
381 | _lentes_ is open source and can be found on
382 | link:https://github.com/funcool/lentes[github].
383 |
384 | You can clone the public repository with this command:
385 |
386 | [source,text]
387 | ----
388 | git clone https://github.com/funcool/lentes
389 | ----
390 |
391 |
392 | === Run tests
393 |
394 | For running tests just execute this:
395 |
396 | .Run tests on node platform
397 | [source, text]
398 | ----
399 | clojure -Adev tools build:tests
400 | node ./target/tests.js
401 | ----
402 |
403 | .Run tests on JVM platform
404 | ----
405 | clojure -Adev -m lentes.tests
406 | ----
407 |
408 |
409 | === License
410 |
411 | _lentes_ is licensed under BSD (2-Clause) license:
412 |
413 | ----
414 | Copyright (c) 2015-2019 Andrey Antukh
415 |
416 | All rights reserved.
417 |
418 | Redistribution and use in source and binary forms, with or without
419 | modification, are permitted provided that the following conditions are met:
420 |
421 | * Redistributions of source code must retain the above copyright notice, this
422 | list of conditions and the following disclaimer.
423 |
424 | * Redistributions in binary form must reproduce the above copyright notice,
425 | this list of conditions and the following disclaimer in the documentation
426 | and/or other materials provided with the distribution.
427 |
428 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
429 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
430 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
431 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
432 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
433 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
434 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
435 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
436 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
437 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
438 | ----
439 |
--------------------------------------------------------------------------------
/src/lentes/core.cljc:
--------------------------------------------------------------------------------
1 | (ns lentes.core
2 | (:refer-clojure :exclude [nth key keys vals filter select-keys cat derive])
3 | (:require [clojure.core :as c]))
4 |
5 | ;; constructors
6 |
7 | (defn lens
8 | "Given a function for getting the focused value from a state
9 | (getter) and a function that takes the state and update
10 | function (setter), constructs a lens."
11 | ([getter]
12 | (fn [next]
13 | (fn
14 | ([s]
15 | (next (getter s)))
16 | ([s f]
17 | (throw (ex-info "Read only lens!" {}))))))
18 | ([getter setter]
19 | (fn [next]
20 | (fn
21 | ([s]
22 | (next (getter s)))
23 | ([s f]
24 | (setter s #(next % f)))))))
25 |
26 | ;; base lenses
27 |
28 | (defn id-setter
29 | "The identity setter, applies the function to the state."
30 | [s f]
31 | (f s))
32 |
33 | (defn const-setter
34 | "The constant setter, returns the state unaltered."
35 | [s _]
36 | s)
37 |
38 | (def id
39 | "Identity lens."
40 | (lens identity id-setter))
41 |
42 | ;; API
43 |
44 | (defn focus
45 | "Given a lens and a state, return the value focused by the lens."
46 | [lens s]
47 | (let [getter (lens identity)]
48 | (getter s)))
49 |
50 | (defn over
51 | "Given a setter, a function and a state, apply the function over
52 | the value focused by the setter."
53 | [st f s]
54 | (let [setter (st id-setter)]
55 | (setter s f)))
56 |
57 | (defn put
58 | "Given a setter, a new value and a state, replace the value focused by
59 | the lens with the new one."
60 | [st v s]
61 | (over st (constantly v) s))
62 |
63 | ;; combinators
64 |
65 | (defn units
66 | "Given a function from unit A to unit B and another in the
67 | opposite direction, construct a lens that focuses and updates
68 | a converted value."
69 | [one->other other->one]
70 | (lens one->other
71 | (fn [s f]
72 | (other->one (f (one->other s))))))
73 |
74 | ;; lenses
75 |
76 | (defn passes
77 | "Given a predicate, return a lens that focuses on an element only
78 | if it passes the predicate.
79 |
80 | The lens is not well-behaved, it depends on the outcome of the predicate."
81 | [applies?]
82 | (lens (fn [s]
83 | (when (applies? s)
84 | s))
85 | (fn [s f]
86 | (if (applies? s)
87 | (f s)
88 | s))))
89 |
90 | (defn nth
91 | "Given a number, returns a lens that focuses on the given index of
92 | a collection."
93 | [n]
94 | (lens (fn [s] (c/nth s n))
95 | (fn [s f] (update s n f))))
96 |
97 | (def fst (nth 0))
98 | (def snd (nth 1))
99 |
100 | (defn- sequential-empty
101 | [coll]
102 | (cond
103 | (map? coll) {}
104 | (set? coll) #{}
105 | :else []))
106 |
107 | (def tail
108 | "A lens into the tail of a collection."
109 | (lens rest
110 | (fn [s f]
111 | (into (sequential-empty s)
112 | (cons (first s)
113 | (f (rest s)))))))
114 |
115 | (defn key
116 | "Given a key, returns a lens that focuses on the given key of
117 | an associative data structure."
118 | [k]
119 | (lens (fn [s] (get s k))
120 | (fn [s f] (update s k f))))
121 |
122 | (defn select-keys
123 | "Return a lens focused on the given keys in an associative data
124 | structure."
125 | [ks]
126 | (lens (fn [s] (c/select-keys s ks))
127 | (fn [s f]
128 | (merge (apply dissoc s ks)
129 | (-> (c/select-keys s ks)
130 | f
131 | (c/select-keys ks))))))
132 |
133 | (defn in
134 | "Given a path and optionally a default value, return a lens that
135 | focuses the given path in an associative data structure."
136 | ([path] (in path nil))
137 | ([path default]
138 | (lens (fn [s] (get-in s path default))
139 | (fn [s f] (update-in s path f)))))
140 |
141 | ;; interop
142 |
143 | (defn- prefix-key
144 | [key id]
145 | (keyword (str id "-" (name key))))
146 |
147 | (def ^:private +empty+ #?(:clj (Object.) :cljs (js/Object.)))
148 |
149 | #?(:clj
150 | (deftype RWFocus [id lens src equals?
151 | ^:unsynchronized-mutable watchers-added
152 | ^:unsynchronized-mutable watchers
153 | ^:unsynchronized-mutable srccache
154 | ^:unsynchronized-mutable cache]
155 |
156 | clojure.lang.IDeref
157 | (deref [self]
158 | (locking self
159 | (let [source (deref src)]
160 | (if (identical? (.-srccache self) source)
161 | (.-cache self)
162 | (let [result (focus lens source)]
163 | (set! (.-srccache self) source)
164 | (set! (.-cache self) result)
165 | result)))))
166 |
167 | clojure.lang.IAtom
168 | (reset [self newval]
169 | (focus lens (swap! src #(put lens newval %))))
170 | (swap [self f]
171 | (focus lens (swap! src (fn [s] (over lens f s)))))
172 | (swap [self f x]
173 | (focus lens (swap! src (fn [s] (over lens #(f % x) s)))))
174 | (swap [self f x y]
175 | (focus lens (swap! src (fn [s] (over lens #(f % x y) s)))))
176 | (swap [self f x y more]
177 | (focus lens (swap! src (fn [s] (over lens #(apply f % x y more) s)))))
178 |
179 | clojure.lang.IAtom2
180 | (resetVals [self newval]
181 | (mapv (partial focus lens) (swap-vals! src #(put lens newval %))))
182 | (swapVals [self f]
183 | (mapv (partial focus lens) (swap-vals! src (fn [s] (over lens f s)))))
184 | (swapVals [self f x]
185 | (mapv (partial focus lens) (swap-vals! src (fn [s] (over lens #(f % x) s)))))
186 | (swapVals [self f x y]
187 | (mapv (partial focus lens) (swap-vals! src (fn [s] (over lens #(f % x y) s)))))
188 | (swapVals [self f x y more]
189 | (mapv (partial focus lens) (swap-vals! src (fn [s] (over lens #(apply f % x y more) s)))))
190 |
191 | clojure.lang.IRef
192 | (addWatch [self key cb]
193 | (locking self
194 | (set! (.-watchers self) (assoc watchers key cb))
195 | (when-not ^boolean (.-watchers-added self)
196 | (set! (.-watchers-added self) true)
197 | (add-watch src id
198 | (fn [_ _ oldv newv]
199 | (when-not (identical? oldv newv)
200 | (let [old' (focus lens oldv)
201 | new' (focus lens newv)]
202 | (set! (.-srccache self) newv)
203 | (set! (.-cache self) new')
204 | (when-not (equals? old' new')
205 | (run! (fn [[key wf]] (wf key self old' new'))
206 | (.-watchers self))))))))
207 | self))
208 |
209 | (removeWatch [self key]
210 | (locking self
211 | (set! (.-watchers self) (dissoc watchers key))
212 | (when (empty? watchers)
213 | (set! (.-watchers-added self) false)
214 | (remove-watch src id)))))
215 |
216 | :cljs
217 | (deftype RWFocus [id lens src equals?
218 | ^:mutable watchers-added
219 | ^:mutable watchers
220 | ^:mutable srccache
221 | ^:mutable cache]
222 | IAtom
223 | IDeref
224 | (-deref [self]
225 | (let [source (deref src)]
226 | (if (identical? (.-srccache self) source)
227 | (.-cache self)
228 | (let [result (focus lens source)]
229 | (set! (.-srccache self) source)
230 | (set! (.-cache self) result)
231 | result))))
232 |
233 | IReset
234 | (-reset! [self newval]
235 | (swap! src #(put lens newval %))
236 | (deref self))
237 |
238 | ISwap
239 | (-swap! [self f]
240 | (swap! src (fn [s] (over lens f s)))
241 | (deref self))
242 | (-swap! [self f x]
243 | (swap! src (fn [s] (over lens #(f % x) s)))
244 | (deref self))
245 | (-swap! [self f x y]
246 | (swap! src (fn [s] (over lens #(f % x y) s)))
247 | (deref self))
248 | (-swap! [self f x y more]
249 | (swap! src (fn [s] (over lens #(apply f % x y more) s)))
250 | (deref self))
251 |
252 | IWatchable
253 | (-add-watch [self key cb]
254 | (set! (.-watchers self) (assoc watchers key cb))
255 | (when-not ^boolean (.-watchers-added self)
256 | (set! (.-watchers-added self) true)
257 | (add-watch src id
258 | (fn [_ _ oldv newv]
259 | (when-not (identical? oldv newv)
260 | (let [old' (focus lens oldv)
261 | new' (focus lens newv)]
262 | (set! (.-srccache self) newv)
263 | (set! (.-cache self) new')
264 | (when-not (equals? old' new')
265 | (run! (fn [[key wf]] (wf key self old' new'))
266 | (.-watchers self))))))))
267 | self)
268 |
269 | (-remove-watch [self key]
270 | (set! (.-watchers self) (dissoc watchers key))
271 | (when (empty? watchers)
272 | (set! (.-watchers-added self) false)
273 | (remove-watch src id)))))
274 |
275 | #?(:clj
276 | (deftype ROFocus [id lens src equals?
277 | ^:unsynchronized-mutable watchers-added
278 | ^:unsynchronized-mutable watchers
279 | ^:unsynchronized-mutable srccache
280 | ^:unsynchronized-mutable cache]
281 | clojure.lang.IDeref
282 | (deref [self]
283 | (locking self
284 | (let [source (deref src)]
285 | (if (identical? (.-srccache self) source)
286 | (.-cache self)
287 | (let [result (focus lens source)]
288 | (set! (.-srccache self) source)
289 | (set! (.-cache self) result)
290 | result)))))
291 |
292 | clojure.lang.IRef
293 | (addWatch [self key cb]
294 | (locking self
295 | (set! (.-watchers self) (assoc watchers key cb))
296 | (when-not ^boolean (.-watchers-added self)
297 | (set! (.-watchers-added self) true)
298 | (add-watch src id
299 | (fn [_ _ oldv newv]
300 | (when-not (identical? oldv newv)
301 | (let [old' (focus lens oldv)
302 | new' (focus lens newv)]
303 | (set! (.-srccache self) newv)
304 | (set! (.-cache self) new')
305 | (when-not (equals? old' new')
306 | (run! (fn [[key wf]] (wf key self old' new'))
307 | (.-watchers self))))))))
308 | self))
309 |
310 | (removeWatch [self key]
311 | (locking self
312 | (set! (.-watchers self) (dissoc watchers key))
313 | (when (empty? watchers)
314 | (set! (.-watchers-added self) false)
315 | (remove-watch src id)))))
316 |
317 | :cljs
318 | (deftype ROFocus [id lens src equals?
319 | ^:mutable watchers-added
320 | ^:mutable watchers
321 | ^:mutable srccache
322 | ^:mutable cache]
323 | IAtom
324 | IDeref
325 | (-deref [self]
326 | (let [source (deref src)]
327 | (if (identical? (.-srccache self) source)
328 | (.-cache self)
329 | (let [result (focus lens source)]
330 | (set! (.-srccache self) source)
331 | (set! (.-cache self) result)
332 | result))))
333 | IWatchable
334 | (-add-watch [self key cb]
335 | (set! (.-watchers self) (assoc watchers key cb))
336 | (when-not ^boolean (.-watchers-added self)
337 | (set! (.-watchers-added self) true)
338 | (add-watch src id
339 | (fn [_ _ oldv newv]
340 | (when-not (identical? oldv newv)
341 | (let [old' (focus lens oldv)
342 | new' (focus lens newv)]
343 | (set! (.-srccache self) newv)
344 | (set! (.-cache self) new')
345 | (when-not (equals? old' new')
346 | (run! (fn [[key wf]] (wf key self old' new'))
347 | (.-watchers self))))))))
348 | self)
349 |
350 | (-remove-watch [self key]
351 | (set! (.-watchers self) (dissoc watchers key))
352 | (when (empty? watchers)
353 | (set! (.-watchers-added self) false)
354 | (remove-watch src id)))))
355 |
356 | (defn derive
357 | "Create a derived atom from another atom with the provided lens.
358 |
359 | The returned atom is lazy, so no code is executed until the user
360 | requires it.
361 |
362 | By default the derived atom does not trigger updates if the data
363 | does not affect it (determined by lens), but this behavior can
364 | be deactivated by passing `:equals?` to `false` as the third options
365 | parameter. You also may pass `=` as `equals?` parameter if you want
366 | value comparison instead of reference comparison with `identical?`.
367 |
368 | You can explicitly create read only refs (not atoms, because the
369 | returned object satisifies watchable and ref but not atom interface)
370 | by passing `:read-only?` as `true` as option on the optional third
371 | parameter."
372 | ([lens src]
373 | (derive lens src nil))
374 | ([lens src {:keys [read-only? equals?]
375 | :or {read-only? false
376 | equals? identical?}}]
377 | (let [id (gensym "lentes-ref")]
378 | (if read-only?
379 | (ROFocus. id lens src equals? false {} +empty+ +empty+)
380 | (RWFocus. id lens src equals? false {} +empty+ +empty+)))))
381 |
--------------------------------------------------------------------------------