Неинтуитивное поведение (для меня) в Reactive.jl
Мой вопрос касается пакета Reactive https://github.com/JuliaLang/Reactive.jl
Я прочитал учебник и экспериментирую, изучая подход реактивного программирования. Я пытаюсь следующий код, и он работает как ожидалось:
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
Это приводит, как и ожидалось, к печати 10 случайных чисел между 590 и 610:
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
Однако вместо вектора c, имеющего десять случайных чисел от 590 до 610, c представляет собой вектор, содержащий значение 500 десять раз:
10-element Array{Any,1}:
500
500
500
500
500
500
500
500
500
500
Я пытаюсь понять, вызвано ли это поведение чем-то, чего я не понимаю в программировании Reactive; возможно объединение для петель и сигналов - нет-нет? Я был бы признателен за понимание того, что вызывает такое поведение.
Для справки, я использую Julia 0.4.5 внутри записной книжки IJulia.
1 ответ
Библиотека Джулии Reactive.jl разработана для того, чтобы разрешить программирование Реактива в Джулии. Суть в том, что только сигналы являются реактивными, и они либо независимы, либо зависят от других сигналов. В примере x
является независимым сигналом, и обновление по умолчанию для сигнала будет вызывать push!
, z
зависит от сигнала x
Именно поэтому он автоматически обновляется, когда x
изменения.
Теперь это только два сигнала, обратите внимание, что c
определяется как Vector()
, который не является сигналом, а обычный массив в Юлии. Таким образом, любой код, который работает на нем, работает только один раз, как и все нереактивные языки. Следовательно
for i in dar
push!(x, i)
push!(c, value(z))
end
будет выделять только c
один раз, когда код запускается впервые, посредством чего z
по-прежнему содержит значение по умолчанию 500
из-за init=500
в коде. Это имеет интуитивный смысл. На самом деле, если c
изменяется из-за z
Тогда мы сделали поведение Юлии основополагающим, чтобы оно было реактивным, и это нестабильно и поэтому нежелательно...
Итак, как мы делаем c
обновлять всякий раз, когда z
делает? Правильный способ - использовать Reactive программирование полностью, и так 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()
внутри такой сценарий высокого уровня.