-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharrows-extra.org~
551 lines (398 loc) · 13.1 KB
/
arrows-extra.org~
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
*ARROWS-EXTRA*
* Overview
This is a library of useful arrows based on Jim
Duey's [[http://www.intensivesystems.net/tutorials/stream_proc.html]conduit
library]
#+name: use-arrows
#+begin_src clojure
(use 'conduit.core 'arrows.core)
#+end_src
#+RESULTS: use-arrows
: ;;=> nil
** Delays
This is a little collection of delay arrows, based on a-loop
*** arr-delay-state
It is often useful to delay some part of a signal with respect to
another part of a signal.
Suppose, for example, you want to detect a rising edge in a signal
(see arr-edge). The plan would be to have [state input], where state
is the last input, and compare input with state
A useful tool to do this is arr-delay-state, which takes [state input]
and delays the state relative to the input by 1 cycle
Obviously it is necessary to supply an initial state which will travel
with the first input
#+name: arr-delay-state
#+begin_src clojure
(defn arr-delay-state [init]
(a-comp
(a-loop (a-arr (fn [[state input]] [state input]))
init
(a-arr (fn [input1] (first (second input1)))))
(a-arr (fn [[state2 [dummy input2]]] [state2 input2]))))
#+end_src
#+RESULTS: arr-delay-state
: ;;=> #'arrows-extra.core/arr-delay-state
#+RESULTS: delay-state
| | #'arrows-extra.core/arr-delay-state |
Test
#+begin_src clojure
(conduit-map (arr-delay-state "hello") [["nice" 1] ["one" 2] ["yes" 3]
["no" 4]])
#+end_src
#+RESULTS:
: ;;=> (["hello" 1] ["nice" 2] ["one" 3] ["yes" 4])
*** arr-delay
Taking this forward, if you want to delay an input by 1 cycle, use
arr-delay. Supply the first output value in init. See the test for how it works
We achieve this by duplicating the input, delaying the first of the
duplicates with arr-delay-state, then getting the first out
#+name: arr-delay
#+begin_src clojure
(defn arr-delay [init]
(a-comp (a-arr (fn [input] [input input]))
(arr-delay-state init)
(a-arr (fn [[input1 input2]] input1)))
)
#+end_src
#+RESULTS:
| | #'arrows-extra.core/arr-delay |
Test
#+begin_src clojure
(conduit-map (arr-delay "hello") ["watch" "as" "the" "delay" "works"])
#+end_src
#+RESULTS:
| | ("hello" "watch" "as" "the" "delay") |
** Information providers
This group contains arrows which tell you something about the input
An edge arrows tells you that the input has risen or fallen
with respect to the last cycle
*** arr-edge
Detect a rising or falling edge when applying a compare-fn to the
input data. The compare-fn takes a two parameters, compares them, and
returns positive, 0 or negative integer.
The input must have an event channel [{:someevent true}
input] into which the rising and falling events will be written as
{:edge :rising} or {:edge :falling}
#+name: arr-edge
#+begin_src clojure
(defn arr-edge [compare-fn]
(a-comp
(a-all
(arr-delay nil)
pass-through)
; :: [[ev1 last] [ev2 next]]
(a-arr (fn [[[ev1 last] [ev2 next]]] [(if last (compare-fn last next) 0) ev2 next]))
; :: [comp ev2 next]
(a-arr (fn [[comp ev data]] [(if (not (= 0 comp)) (assoc ev :edge (if (> 0 comp) :rising :falling)) ev) data]))
)
)
#+end_src
#+RESULTS: arr-edge
| | #'arrows-extra.core/arr-edge |
Test
#+begin_src clojure
(conduit-map (a-comp
;add an event channel
(a-all (a-arr (constantly {}))
pass-through)
(arr-edge (fn [last input] (- last input)))
)
[0 0 1 2 2 1 0])
#+end_src
#+RESULTS:
| | ([{} 0] [{} 0] [{:edge :rising} 1] [{:edge :rising} 2] [{} 2] [{:edge :falling} 1] [{:edge :falling} 0]) |
** Switches
A switch sends a signal to different paths depending on a predicate. a-select is of
course an example of a switch. Here are some more
*** arr-switch
Although a-select and a-selectp allow switching between channels, it
useful to generalise these with an arrow which can maintain a state
and switch by reference to that state i.e. composing a-select with
a-loop, as it were
For example, the arr-switch-between arrow will switch the first n
instances to channel1 and the remaining instances to channel 2. For
this purpose it needs to maintain a count. It uses arr-switch to do
this. It passes the state updating function (inc state) and the
testing predicate (>= state lower)
#+name: arr-switch-between
#+begin_src clojure
(defn arr-switch-between [lower upper trueChannel falseChannel]
(arr-switch -1 (fn [state input] (inc state)) (fn [state] (and (>= state lower) (<= state upper))) trueChannel falseChannel)
)
#+end_src
#+RESULTS: arr-switch-between
| | #'arrows-extra.core/arr-switch-between |
So the arr-switch arrow itself is basically an a-loop followed adding
a true false channel based on the predicate, followed by an a-select:
#+name: arr-switch
#+begin_src clojure
(defmacro arr-switch [init state-fn pred-fn trueChannel falseChannel]
`(a-comp
(a-loop (a-arr (fn [[state# input#]] [(~state-fn state# input#) input#]))
~init
(a-arr first)
)
(a-par
(a-arr (fn [state#] (~pred-fn state#)))
pass-through
)
(a-select true ~trueChannel false ~falseChannel)
)
)
#+end_src
#+RESULTS:
| | #'arrows-extra.core/arr-switch |
Test this using arr-switch-between
#+begin_src clojure
(conduit-map (arr-switch-between 2 4 (a-arr (fn[input] (str
"channel1:" input))) (a-arr (fn[input] (str "channel2:" input))))
(range 0 10))
#+end_src
#+RESULTS:
: ;;=> ("channel2:0" "channel2:1" "channel1:2" "channel1:3" "channel1:4" "channel2:5" "channel2:6" "channel2:7" "channel2:8" "channel2:9")
** Gates
*** Block
It is often necessary to block one branch of an arrow
e.g. (a-select :yes pass-through :no block)
#+name: block
#+begin_src clojure
(def-proc block [input]
[]
)
#+end_src
#+RESULTS: block
| | #'arrows-extra.core/block |
*** Filter
Filter a signal by predicate
e.g. (a-comp arr-sometimes-nil-producer (arr-filter nil?))
#+name: arr-filter
#+begin_src clojure
(defn arr-filter [pred]
(a-if (partial apply pred) pass-through block)
)
#+end_src
*** arr-toggle
A toggle takes two predicates: pred-on and pred-off. It outputs
until pred-on is true until pred-off is true
#+name: arr-toggle
#+begin_src clojure
(defn arr-toggle [pred-on pred-off]
(a-comp
(a-loop
(a-arr (fn [[state input]] [(if (pred-off input) false (or state
(pred-on input))) input]))
false
(a-arr first))
(a-select true pass-through false block)
)
)
#+end_src
#+RESULTS: arr-toggle
| | #'arrows-extra.core/arr-toggle |
Test
#+begin_src clojure
(conduit-map (arr-toggle (fn [input] (== input 3)) (fn [input] (==
input 5))) (range 0 10))
#+end_src
#+RESULTS:
| | (3 4) |
*** arr-toggle-inclusive
As for arr-toggle except that the data which triggered pred-off is forwarded
We do this by feeding [state data] through an edge detector, and where
a falling edge is detected,
ensuring state is 'true' :: [data] -> [data] | []"
#+name: arr-toggle-inclusive
#+begin_src clojure
(defn- boolean-to-int [value]
(if value 1 0)
)
(defn arr-toggle-inclusive [pred-on pred-off]
(a-comp (a-loop (a-arr (fn [[state input]] [(if (pred-off input) false (or state (pred-on input))) input])) false (a-arr first))
; (a-arr (fn [data] [{} data]))
(a-all (a-arr (constantly {})) pass-through)
(arr-edge (fn [lst nxt] (- (boolean-to-int (first lst)) (boolean-to-int (first nxt)))))
(a-arr (fn [[ev [state data]]] [(or state (= (:edge ev) :falling)) data]))
(a-select true pass-through false block)
)
)
#+end_src
#+RESULTS: arr-toggle-inclusive
| | #'arrows-extra.core/arr-toggle-inclusive |
Test
#+begin_src clojure
(conduit-map (arr-toggle-inclusive (fn [input] (== input 3)) (fn [input] (==
input 5))) (range 0 10))
#+end_src
#+RESULTS:
| | (3 4 5) |
*** arr-gate
This is a switch into a block or pass-through. So you can gate a
channel depending on a state function
#+name: arr-gate
#+begin_src clojure
(defn arr-gate [init state-fn pred-fn]
(arr-switch init state-fn pred-fn pass-through block)
)
#+end_src
#+RESULTS: arr-gate
| | #'arrows-extra.core/arr-gate |
Test with arr-between, which allows the cycles from n to m to pass
through
#+name: arr-between
#+begin_src clojure
(defn arr-between [lower upper]
(arr-gate -1 (fn [state input] (inc state)) (fn [state] (and (>= state lower) (<= state upper))))
)
(def arr-once
(arr-between 0 0)
)
#+end_src
#+RESULTS: arr-between
| | #'arrows-extra.core/arr-once |
#+begin_src clojure
(conduit-map (arr-between 2 4) (range 0 10))
#+end_src
#+RESULTS:
| | (2 3 4) |
** Accumulators
*** arr-accum and arr-accum-greedy
These are the arrow equivalents of the reduce function, reducing the
time series using accum-fn until the predicate function returns
true. At this point, the arrow spits out the accumulated value.
In the greedy version, the state is also updated once when the predicate is
true.
Both accum-fn and pred a 2-ary functions of [state input]
I have recently 7 11 2012 added an optional strip-fn which if present is applied to
state as the state is sput out.
#+name: arr-accum
#+begin_src clojure
(defn arr-accum
[init accum-fn pred & [strip-fn]]
(a-comp (a-loop (a-arr (fn [[state input]] (if (pred state input)
[state input] [(accum-fn state input) input])))
init
(a-arr (fn [[ state input]] (if (pred state input) init
state))))
(a-selectp (fn [[state input]] (pred state input)) true (a-arr (if strip-fn (fn [[state input]] (strip-fn state)) first)) false block))
)
(defn arr-accum-greedy [init accum-fn pred & [strip-fn]]
(a-comp (a-loop (a-arr (fn [[state input]] [(accum-fn state input)
input]))
init (a-arr first))
(a-selectp (fn [[state input]] (pred state input)) true (a-arr (if strip-fn (fn [[state input]] (strip-fn state)) first)) false block))
)
#+end_src
#+RESULTS: arr-accum
: ;;=> #'arrows-extra.core/arr-accum-greedy
#+RESULTS:
| | #'arrows-extra.core/arr-accum-greedy |
A useful accumulator is a counting accumulator - accumulate using accum-fn over n cycles,
then spit the result
#+begin_src clojure
(defn arr-accum-counting [init accum-fn n]
(arr-accum [0 init] (fn [[count state] input] [(inc count) (accum-fn state input)])
(fn [[count state] input] (== count n))
(fn [[count state]] state)
)
)
#+end_src
#+RESULTS:
: ;;=> #'arrows-extra.core/arr-accum-counting
Test
#+begin_src clojure
(conduit-map (arr-switch-between 0 2 (arr-accum-counting "" (fn [state input] (str state input)) 3)
(arr-switch-between 0 3 (arr-accum-counting "" (fn [state input] (str state input)) 4) pass-through))
["a" "b" "c" "d" "e" "f" "g"])
#+end_src
#+RESULTS:
: ;;=> ("abc" "defg")
** Interfacing between arrows
*** Flattening
#+name: arr-flatten
#+begin_src clojure
(def-proc arr-flatten [input]
(if (seq? input) input [input])
)
#+end_src
#+RESULTS: arr-flatten
: ;;=> #'arrows-extra.core/arr-flatten
Test
#+begin_src clojure
(conduit-map (arr-flatten 1 4) ["a" ["b" "c"] "d" "e"])
#+end_src
#+RESULTS:
: ;;=> ("a" ["b" "c"] "d" "e")
*** lifting and dropping
It is often the case that the output from one arrow needs to be lifted
or dropped
[state] -> [[state] ] is a lift
and [[state] ] -> [state] is a drop
#+name: arr-lift-drop
#+begin_src clojure
(def-proc arr-lift [input]
[[input]]
)
(def arr-drop
(a-par pass-through)
)
#+end_src
#+RESULTS:
| | #'arrows-extra.core/arr-drop |
*** Partitioning
The logic here is you have an input which looks like [a b c d]
and you need to feed an arrow which takes a b and another which takes c d
Looks like we need to return an accumulator for 0, 1 and another accumulator for 2 3
This is recursive. There's a arr-switch-between either to the counting accumulator or onwards
#+name: arr-partition
#+begin_src clojure
(defn arr-partition
([length channel & more]
(arr-switch-between 0 (dec length)
(a-comp
(arr-accum-counting [] (fn [state input] (concat state [input])) length)
channel)
(apply arr-partition more)
))
([]
pass-through
)
)
#+end_src
#+RESULTS:
: ;;=> #'arrows-extra.core/arr-partition
Test
#+begin_src clojure
(conduit-map (arr-partition 2 (a-arr (fn [p] (str "a1" p)))
2 (a-arr (fn [p] (str "a2" p)))
)
["h1" "h2" "h3" "h4"])
#+end_src
#+RESULTS:
: ;;=> ("a1clojure.lang.LazySeq@19ce2" "a2clojure.lang.LazySeq@19d22")
* Tangle
#+begin_src clojure :tangle src/arrows_extra/core.clj :noweb yes
(ns arrows-extra.core)
<<use-arrows>>
<<block>>
<<arr-filter>>
<<arr-delay-state>>
<<arr-delay>>
<<arr-edge>>
<<arr-switch>>
<<arr-switch-between>>
<<arr-toggle>>
<<arr-toggle-inclusive>>
<<arr-gate>>
<<arr-between>>
<<arr-once>>
<<arr-accum>>
<<arr-lift-drop>>
<<arr-partition>>
#+end_src
#+begin_src clojure :tangle project.clj
(defproject arrows-extra "1.0.0"
:description "Arrows extra"
:dependencies [[org.clojure/clojure "1.2.1"]
[net.intensivesystems/conduit "0.8.1"]
])
#+end_src