.. module:: yarp Creating impure Values ====================== In general, ``yarp`` :py:class:`Value`\ s are intended to be passed between simple, pure_ functions wrapped by the :py:func:`fn` decorator. Specifically, these functions don't hold any state and the resulting :py:class:`Value`\ s change when-and-only-when any input :py:class:`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 :py:func:`delay` using such a function since input :py;class:`Value` changes do not immediately result in the :py:class:`Value` changing. Simillarly, the :py:func:`no_repeat` function also cannot be replicated since it doesn't always change its output :py:class:`Value` when its input changes. .. _pure: https://en.wikipedia.org/wiki/Pure_function To get around this limitation it is necessary to manipulate :py:class:`Value`\ s 'by hand'. Lets begin by seeing how :py:func:`no_repeat` is implemented. The following pseudo code implementation goes the 'obvious' implementation for a no-repeat value: .. code-block:: text 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: .. doctest:: :hide: >>> from yarp import NoValue, Value .. doctest:: >>> 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 :py:class:`Value` using :py:meth:`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 :py:class:`Value` is updated. If ``source_value`` is a continuous function we could update the output using either: .. code-block:: python output_value.value = new_value Or: .. code-block:: python output_value.value = source_value.value However, if ``source_value`` is an instantaneous value, we'd need to do use :py:meth:`Value.set_instantaneous_value`: .. code-block:: python output_value.set_instantaneous_value(new_value) Since we'd like to make our output :py:class:`Value` mimic the input regardless of whether it is continuous or instantaneous, instead we use the following two-step process: .. code-block:: python output_value._value = source_value.value output_value.set_instantaneous_value(new_value) By setting ``_value`` we change :py:attr:`Value.value` without triggering any callbacks registered with :py:meth:`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 :py:meth:`Value.set_instantaneous_value` with the just-received value from the source we cause the callback to occur in the output :py:class:`Value`. You can try it out, first lets try a continuous value: .. doctest:: >>> # Create a value to de-repeat >>> v = Value(123) >>> nrv = no_repeat(v) >>> nrv.on_value_changed(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: .. doctest:: >>> # Create another instantaneous value to de-repeat >>> iv = Value() >>> nriv = no_repeat(iv) >>> nriv.on_value_changed(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