-
Notifications
You must be signed in to change notification settings - Fork 10
/
core.cljc
347 lines (302 loc) · 9.76 KB
/
core.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
(ns lentes.core
(:refer-clojure :exclude [nth key keys vals filter select-keys cat derive])
(:require [clojure.core :as c]))
;; constructors
(defn lens
"Given a function for getting the focused value from a state
(getter) and a function that takes the state and and update
function (setter), constructs a lens."
([getter]
(fn [next]
(fn
([s]
(next (getter s)))
([s f]
(throw (ex-info "Read only lense!" {}))))))
([getter setter]
(fn [next]
(fn
([s]
(next (getter s)))
([s f]
(setter s #(next % f)))))))
;; base lenses
(defn id-setter
"The identity setter, applies the function to the state."
[s f]
(f s))
(defn const-setter
"The constant setter, returns the state unaltered."
[s _]
s)
(def id
"Identity lens."
(lens identity id-setter))
;; API
(defn focus
"Given a lens and a state, return the value focused by the lens."
[lens s]
(let [getter (lens identity)]
(getter s)))
(defn over
"Given a setter, a function and a state, apply the function over
the value focused by the setter."
[st f s]
(let [setter (st id-setter)]
(setter s f)))
(defn put
"Given a setter, a new value and a state, replace the value focused by
the lens with the new one."
[st v s]
(over st (constantly v) s))
;; combinators
(defn units
"Given a function from unit A to unit B and another in the
opposite direction, construct a lens that focuses and updates
a converted value."
[one->other other->one]
(lens one->other
(fn [s f]
(other->one (f (one->other s))))))
;; lenses
(defn passes
"Given a predicate, return a lens that focuses in an element only
if passes the predicate.
The lens is not well-behaved, depens on the outcome of the predicate."
[applies?]
(lens (fn [s]
(when (applies? s)
s))
(fn [s f]
(if (applies? s)
(f s)
s))))
(defn nth
"Given a number, returns a lens that focuses on the given index of
a collection."
[n]
(lens (fn [s] (c/nth s n))
(fn [s f] (update s n f))))
(def fst (nth 0))
(def snd (nth 1))
(defn- sequential-empty
[coll]
(cond
(map? coll) {}
(set? coll) #{}
:else []))
(def tail
"A lens into the tail of a collection."
(lens rest
(fn [s f]
(into (sequential-empty s)
(cons (first s)
(f (rest s)))))))
(defn key
"Given a key, returns a lens that focuses on the given key of
an associative data structure."
[k]
(lens (fn [s] (get s k))
(fn [s f] (update s k f))))
(defn select-keys
"Return a lens focused on the given keys in an associative data
structure."
[ks]
(lens (fn [s] (c/select-keys s ks))
(fn [s f]
(merge (apply dissoc s ks)
(-> (c/select-keys s ks)
f
(c/select-keys ks))))))
(defn in
"Given a path and optionally a default value, return a lens that
focuses the given path in an associative data structure."
([path] (in path nil))
([path default]
(lens (fn [s] (get-in s path default))
(fn [s f] (update-in s path f)))))
;; interop
(defn- prefix-key
[key id]
(keyword (str id "-" (name key))))
(defn- make-watcher
[self lens equals?]
(letfn [(run-watchers [oldv newv]
(run! (fn [[key wf]] (wf key self oldv newv))
(.-watchers self)))]
(fn [_ _ oldv newv]
(when-not (identical? newv (.-srccache self))
(let [old' (focus lens oldv)
new' (focus lens newv)]
(set! (.-cache self) new')
(set! (.-oldcache self) old')
(set! (.-srccache self) newv)
(when-not (equals? old' new')
(run-watchers old' new')))))))
#?(:clj
(deftype RWFocus [id lens src equals?
^:unsynchronized-mutable watchers
^:unsynchronized-mutable srccache
^:unsynchronized-mutable oldcache
^:unsynchronized-mutable cache]
clojure.lang.IDeref
(deref [self]
(locking self
(let [source (deref src)]
(if (identical? srccache source)
cache
(let [result (focus lens source)]
(set! (.-srccache self) source)
(set! (.-oldcache self) (.-cache self))
(set! (.-cache self) result)
result)))))
clojure.lang.IAtom
(reset [self newval]
(swap! src #(put lens newval %))
(deref self))
(swap [self f]
(swap! src (fn [s] (over lens f s)))
(deref self))
(swap [self f x]
(swap! src (fn [s] (over lens #(f % x) s)))
(deref self))
(swap [self f x y]
(swap! src (fn [s] (over lens #(f % x y) s)))
(deref self))
(swap [self f x y more]
(swap! src (fn [s] (over lens #(apply f % x y more) s)))
(deref self))
clojure.lang.IRef
(addWatch [self key cb]
(locking self
(set! (.-watchers self) (assoc watchers key cb))
(when (= (count (.-watchers self)) 1)
(add-watch src id (make-watcher self lens equals?)))
self))
(removeWatch [self key]
(locking self
(set! (.-watchers self) (dissoc watchers key))
(when (empty? watchers)
(remove-watch src id)))))
:cljs
(deftype RWFocus [id lens src equals?
^:mutable watchers
^:mutable srccache
^:mutable oldcache
^:mutable cache]
IAtom
IDeref
(-deref [self]
(let [source (deref src)]
(if (identical? srccache source)
cache
(let [result (focus lens source)]
(set! (.-srccache self) source)
(set! (.-oldcache self) (.-cache self))
(set! (.-cache self) result)
result))))
IReset
(-reset! [self newval]
(swap! src #(put lens newval %))
(deref self))
ISwap
(-swap! [self f]
(swap! src (fn [s] (over lens f s)))
(deref self))
(-swap! [self f x]
(swap! src (fn [s] (over lens #(f % x) s)))
(deref self))
(-swap! [self f x y]
(swap! src (fn [s] (over lens #(f % x y) s)))
(deref self))
(-swap! [self f x y more]
(swap! src (fn [s] (over lens #(apply f % x y more) s)))
(deref self))
IWatchable
(-add-watch [self key cb]
(set! (.-watchers self) (assoc watchers key cb))
(when (= (count (.-watchers self)) 1)
(add-watch src id (make-watcher self lens equals?)))
self)
(-remove-watch [self key]
(set! (.-watchers self) (dissoc watchers key))
(when (empty? watchers)
(remove-watch src id)))))
#?(:clj
(deftype ROFocus [id lens src equals?
^:unsynchronized-mutable watchers
^:unsynchronized-mutable srccache
^:unsynchronized-mutable oldcache
^:unsynchronized-mutable cache]
clojure.lang.IDeref
(deref [self]
(locking self
(let [source (deref src)]
(if (identical? srccache source)
cache
(let [result (focus lens source)]
(set! (.-srccache self) source)
(set! (.-oldcache self) (.-cache self))
(set! (.-cache self) result)
result)))))
clojure.lang.IRef
(addWatch [self key cb]
(locking self
(set! (.-watchers self) (assoc watchers key cb))
(when (= (count (.-watchers self)) 1)
(add-watch src id (make-watcher self lens equals?)))
self))
(removeWatch [self key]
(locking self
(set! (.-watchers self) (dissoc watchers key))
(when (empty? watchers)
(remove-watch src id)))))
:cljs
(deftype ROFocus [id lens src equals?
^:mutable watchers
^:mutable srccache
^:mutable oldcache
^:mutable cache]
IDeref
(-deref [self]
(let [source (deref src)]
(if (identical? srccache source)
cache
(let [result (focus lens source)]
(set! (.-srccache self) source)
(set! (.-oldcache self) (.-cache self))
(set! (.-cache self) result)
result))))
IWatchable
(-add-watch [self key cb]
(set! (.-watchers self) (assoc watchers key cb))
(when (= (count (.-watchers self)) 1)
(add-watch src id (make-watcher self lens equals?)))
self)
(-remove-watch [self key]
(set! (.-watchers self) (dissoc watchers key))
(when (empty? watchers)
(remove-watch src id)))))
(def ^:private +empty+ #?(:clj (Object.) :cljs (js/Object.)))
(defn derive
"Create a derived atom from an other atom with the provided lense.
The returned atom is lazy, so no code is executed until user
requires it.
By default the derived atom does not trigger updates if the data
does not affects to it (determined by lense), but this behavior can
be deactivated passing `:equals?` to `false` on the third options
parameter. You also may pass `=` as `equals?` parameter if you want
value comparison instead of reference comparison with `identical?`.
You can create expliclitly read only refs (not atoms, because the
returned object satisifies watchable and ref but not atom interface)
passing `:read-only?` as `true` as option on the optional third
parameter."
([lens src]
(derive lens src nil))
([lens src {:keys [read-only? equals?]
:or {read-only? false
equals? identical?}}]
(let [id (gensym "lentes-ref")]
(if read-only?
(ROFocus. id lens src equals? nil +empty+ +empty+ +empty+)
(RWFocus. id lens src equals? nil +empty+ +empty+ +empty+)))))