Creating impure Values¶
In general, yarp
Value
s are intended to be passed between
simple, pure functions wrapped by the fn()
decorator. Specifically,
these functions don’t hold any state and the resulting Value
s
change when-and-only-when any input Value
changes. This type of
function is very easy to write and reason about with yarp
but is
fundamentally constrained. For example, it is not possible to implement
delay()
using such a function since input :py;class:Value changes do
not immediately result in the Value
changing. Simillarly, the
no_repeat()
function also cannot be replicated since it doesn’t always
change its output Value
when its input changes.
To get around this limitation it is necessary to manipulate Value
s ‘by hand’. Lets begin by seeing how no_repeat()
is implemented.
The following pseudo code implementation goes the ‘obvious’ implementation for a no-repeat value:
on source value changed:
if source value != last source value:
output value = source value
last source value = source value
The actual Python implementation looks like:
>>> def no_repeat(source_value):
... last_value = source_value.value
...
... # Initially take on the source value
... output_value = Value(last_value)
...
... @source_value.on_value_changed
... def on_source_value_changed(new_value):
... nonlocal last_value
... if new_value != last_value:
... last_value = new_value
... # Copy to output whether continuous or instantaneous
... output_value._value = source_value.value
... output_value.set_instantaneous_value(new_value)
...
... return output_value
In this example we create function (or rather, a closure) called
on_source_value_changed
and set it as the callback for the source
Value
using Value.on_value_changed()
.
Note
This example uses the Python decorator syntax making the code read a little more naturally, as in the pseudo-code version.
The last_value
variable is accessed from the enclosing scope is used to
keep track of the last value received from the source. The nonlocal keyword is
used to gain access to it from our callback.
The last detail is the way the output Value
is updated. If
source_value
is a continuous function we could update the output using
either:
output_value.value = new_value
Or:
output_value.value = source_value.value
However, if source_value
is an instantaneous value, we’d need to do use
Value.set_instantaneous_value()
:
output_value.set_instantaneous_value(new_value)
Since we’d like to make our output Value
mimic the input regardless
of whether it is continuous or instantaneous, instead we use the following
two-step process:
output_value._value = source_value.value
output_value.set_instantaneous_value(new_value)
By setting _value
we change Value.value
without triggering any
callbacks registered with Value.on_value_changed()
. We set this to the
continuous value of the source (which is :py:data`NoValue` if the source is
instantaneous). By calling Value.set_instantaneous_value()
with the
just-received value from the source we cause the callback to occur in the
output Value
.
You can try it out, first lets try a continuous value:
>>> # Create a value to de-repeat
>>> v = Value(123)
>>> nrv = no_repeat(v)
>>> nrv.on_value_changed(print)
<built-in function print>
>>> # Repeated values should not pass through
>>> v.value = 321
321
>>> v.value = 321
>>> v.value = 321
>>> v.value = 123
123
Next lets try an instantaneous value:
>>> # Create another instantaneous value to de-repeat
>>> iv = Value()
>>> nriv = no_repeat(iv)
>>> nriv.on_value_changed(print)
<built-in function print>
>>> nriv.value is NoValue
True
>>> iv.set_instantaneous_value(123)
123
>>> nriv.value is NoValue
True
>>> iv.set_instantaneous_value(123)
>>> nriv.value is NoValue
True
>>> iv.set_instantaneous_value(321)
321
>>> nriv.value is NoValue
True