/
operators.jl
307 lines (254 loc) · 8.34 KB
/
operators.jl
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
import Base: map, merge, filter
if isdefined(Base, :foreach)
import Base.foreach
end
export map,
probe,
filter,
filterwhen,
foldp,
sampleon,
merge,
previous,
delay,
droprepeats,
flatten,
bind!,
unbind!
"""
map(f, s::Signal...) -> signal
Transform signal `s` by applying `f` to each element. For multiple signal arguments, apply `f` elementwise.
"""
function map(f, input::Signal, inputsrest::Signal...;
init=f(map(value, (input,inputsrest...))...),
typ=typeof(init), name=auto_name!("map", input, inputsrest...))
n = Signal(typ, init, (input,inputsrest...); name=name)
connect_map(f, n, input, inputsrest...)
n
end
function connect_map(f, output, inputs...)
let prev_timestep = 0
for inp in inputs
add_action!(inp, output) do output, timestep
if prev_timestep != timestep
result = f(map(value, inputs)...)
send_value!(output, result, timestep)
prev_timestep = timestep
end
end
end
end
end
probe(node, name, io=STDERR) =
map(x -> println(io, name, " >! ", x), node)
"""
foreach(f, inputs...)
Same as `map`, but will be prevented from gc until all the inputs have gone out of scope. Should be used in cases where `f` does a side-effect.
"""
foreach(f, in1::Signal, inputs::Signal...; kwargs...) = preserve(map(f, in1, inputs...; kwargs...))
"""
filter(f, signal)
remove updates from the signal where `f` returns `false`.
"""
function filter{T}(f::Function, default, input::Signal{T}; name=auto_name!("filter", input))
n = Signal(T, f(value(input)) ? value(input) : default, (input,); name=name)
connect_filter(f, default, n, input)
n
end
function connect_filter(f, default, output, input)
add_action!(input, output) do output, timestep
val = value(input)
f(val) && send_value!(output, val, timestep)
end
end
"""
filterwhen(switch::Signal{Bool}, default, input)
Keep updates to `input` only when `switch` is true.
If switch is false initially, the specified default value is used.
"""
function filterwhen{T}(predicate::Signal{Bool}, default, input::Signal{T};
name=auto_name!("filterwhen", predicate, input))
n = Signal(T, value(predicate) ? value(input) : default, (input,); name=name)
connect_filterwhen(n, predicate, input)
n
end
function connect_filterwhen(output, predicate, input)
add_action!(input, output) do output, timestep
value(predicate) && send_value!(output, value(input), timestep)
end
end
"""
foldp(f, init, input)
[Fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function)) over past values.
Accumulate a value as the `input` signal changes. `init` is the initial value of the accumulator.
`f` should take 2 arguments: the current accumulated value and the current update, and result in the next accumulated value.
"""
function foldp(f::Function, v0, inputs...; typ=typeof(v0), name=auto_name!("foldp", inputs...))
n = Signal(typ, v0, inputs; name=name)
connect_foldp(f, v0, n, inputs)
n
end
function connect_foldp(f, v0, output, inputs)
let acc = v0
for inp in inputs
add_action!(inp, output) do output, timestep
vals = map(value, inputs)
acc = f(acc, vals...)
send_value!(output, acc, timestep)
end
end
end
end
"""
sampleon(a, b)
Sample the value of `b` whenever `a` updates.
"""
function sampleon{T}(sampler, input::Signal{T}; name=auto_name!("sampleon", input))
n = Signal(T, value(input), (sampler, input); name=name)
connect_sampleon(n, sampler, input)
n
end
function connect_sampleon(output, sampler, input)
add_action!(sampler, output) do output, timestep
send_value!(output, value(input), timestep)
end
end
"""
merge(inputs...)
Merge many signals into one. Returns a signal which updates when
any of the inputs update. If many signals update at the same time,
the value of the *youngest* input signal is taken.
"""
function merge(in1::Signal, inputs::Signal...; name=auto_name!("merge", in1, inputs...))
n = Signal(typejoin(map(eltype, (in1, inputs...))...), value(in1), (in1, inputs...); name=name)
connect_merge(n, in1, inputs...)
n
end
function connect_merge(output, inputs...)
let prev_timestep = 0
for inp in inputs
add_action!(inp, output) do output, timestep
# don't update twice in the same timestep
if prev_timestep != timestep
send_value!(output, value(inp), timestep)
prev_time = timestep
end
end
end
end
end
"""
previous(input, default=value(input))
Create a signal which holds the previous value of `input`.
You can optionally specify a different initial value.
"""
function previous{T}(input::Signal{T}, default=value(input); name=auto_name!("previous", input))
n = Signal(T, default, (input,); name=name)
connect_previous(n, input)
n
end
function connect_previous(output, input)
let prev_value = value(input)
add_action!(input, output) do output, timestep
send_value!(output, prev_value, timestep)
prev_value = value(input)
end
end
end
"""
delay(input, default=value(input))
Schedule an update to happen after the current update propagates
throughout the signal graph.
Returns the delayed signal.
"""
function delay{T}(input::Signal{T}, default=value(input); name=auto_name!("delay", input))
n = Signal(T, default, (input,); name=name)
connect_delay(n, input)
n
end
function connect_delay(output, input)
add_action!(input, output) do output, timestep
push!(output, value(input))
end
end
"""
droprepeats(input)
Drop updates to `input` whenever the new value is the same
as the previous value of the signal.
"""
function droprepeats{T}(input::Signal{T}; name=auto_name!("droprepeats", input))
n = Signal(T, value(input), (input,); name=name)
connect_droprepeats(n, input)
n
end
function connect_droprepeats(output, input)
let prev_value = value(input)
add_action!(input, output) do output, timestep
if prev_value != value(input)
send_value!(output, value(input), timestep)
prev_value = value(input)
end
end
end
end
"""
flatten(input::Signal{Signal}; typ=Any)
Flatten a signal of signals into a signal which holds the
value of the current signal. The `typ` keyword argument specifies
the type of the flattened signal. It is `Any` by default.
"""
function flatten(input::Signal; typ=Any, name=auto_name!("flatten", input))
n = Signal(typ, value(value(input)), (input,); name=name)
connect_flatten(n, input)
n
end
function connect_flatten(output, input)
let current_node = value(input),
callback = (output, timestep) -> begin
send_value!(output, value(value(input)), timestep)
end
add_action!(callback, current_node, output)
add_action!(input, output) do output, timestep
# Move around action from previous node to current one
remove_action!(callback, current_node, output)
current_node = value(input)
add_action!(callback, current_node, output)
send_value!(output, value(current_node), timestep)
end
end
end
const _bindings = Dict()
"""
bind!(a,b,twoway=true)
for every update to `a` also update `b` with the same value and vice-versa.
To only bind updates from b to a, pass in a third argument as `false`
"""
function bind!(a::Signal, b::Signal, twoway=true)
let current_timestep = 0
action = add_action!(b, a) do a, timestep
if current_timestep != timestep
current_timestep = timestep
send_value!(a, value(b), timestep)
end
end
_bindings[a=>b] = action
end
if twoway
bind!(b, a, false)
end
end
"""
unbind!(a,b,twoway=true)
remove a link set up using `bind!`
"""
function unbind!(a::Signal, b::Signal, twoway=true)
if !haskey(_bindings, a=>b)
return
end
action = _bindings[a=>b]
b.actions = filter(x->x!=action, b.actions)
delete!(_bindings, a=>b)
if twoway
unbind!(b, a, false)
end
end