我的问题与反应包https://github.com/JuliaLang/Reactive.jl
有关我已阅读tutorial并正在尝试,了解反应式编程方法。我尝试以下代码,它按预期工作:
using Reactive
x = Signal(100)
z = map(v -> v + 500, x; typ=Int64, init=500)
dar = rand(90:110,10)
for i in dar
push!(x, i)
println(value(z))
end
正如预期的那样,这导致590和610之间的10个随机数被打印出来:
500
591
609
609
605
593
602
596
590
594
到目前为止,这么好。现在,假设我想在每次更新后收集信号z的输出,比如在矢量c:
中using Reactive
x = Signal(100)
z = map(v -> v + 500, x; typ=Int64, init=500)
dar = rand(90:110,10)
c = Vector()
for i in dar
push!(x, i)
push!(c, value(z))
end
然而,在590和610之间有10个随机数的Vector c,c是一个包含500次值的Vector:
10-element Array{Any,1}:
500
500
500
500
500
500
500
500
500
500
我试图了解这种行为是否是由我对Reactive编程无法理解的事情引起的;也许结合 for loops 和 Signals 是不是没有?如果您对导致此行为的原因有所了解,我将不胜感激。
作为参考,我在IJulia笔记本中使用Julia 0.4.5。
答案 0 :(得分:2)
Julia的Reactive.jl库旨在允许Julia中的Reactive programming。关键是只有信号是反应性的,它们要么独立,要么依赖于其他信号。在示例中,x
是一个独立信号,信号的默认更新将调用push!
。 z
上的x
是相关信号,这就是x
更改时自动更新的原因。
现在,这是唯一的两个信号,请注意c
被定义为Vector()
,这不是信号,而是Julia中的正常数组。因此,在其上运行的任何代码只执行一次,就像所有非反应性语言一样。因此
for i in dar
push!(x, i)
push!(c, value(z))
end
在代码首次运行时,只会分配到c
一次,因此代码中z
仍会保留500
的默认值init=500
。这具有直观意义。事实上,如果由c
导致z
发生变化,那么我们已经将Julia的基本行为变为反应性,这是不稳定的,因此是不可取的......
那么,只要c
,我们如何更新z
?正确的方法是一直使用反应式编程,因此c
应该是z
的相关信号。由于c
维持z
的状态,因此反应式编程中的正确结构为foldp
,代表“折叠过去的值”。
有效的代码如下: -
using Reactive
x = Signal(100)
z = map(v -> v + 500, x)
c = foldp((acc, value) -> push!(acc, value), Int[], z)
for i in rand(90:110, 10)
push!(x, i)
yield() #Can also use Reactive.run_till_now()
println(value(z))
end
@show value(c)
您将c
成为z
之前所有值的数组(除了初始值之外,所以如果您想要初始值也可以轻松完成)。请注意,反应式编程保留了类似的代码复杂性,但增加了反应性功能。这使代码更优雅,更易于维护。
评论中建议使用yield()
或Reactive.run_till_now()
,因此我将略过解释。但我的意见是,如果你需要这样做,那么你可能没有正确使用被动编程,或者你的问题会更好地适应其他范例。
这就是我写的: -
using Reactive
x = Signal(100)
z = map(x) do v
result = v + 500
println(result)
return result
end
c = foldp(Int[], z) do acc, value
push!(acc, value)
end
for i in rand(90:110, 10)
push!(x, i)
end
@show value(c)
请注意,被动部分是自我描述的。现在z
会在更新时自行打印,这是描述性而非强制性的。因此,即使更新是异步的,我们仍会捕获z
的更新。推送到x
的命令性代码本身就是模块化的。代码是高级别且易于阅读,没有低级例程在这样的高级脚本中传递yield()
等函数。