meshed.scrap.reactive_scope
Ideas towards a reactive-programming interpretation of meshes. A scope (MutableMapping –think dict-like) that reacts to writes by computing associated functions, themselves writing in the scope, creating a chain reaction that propagates information through the scope.
- class meshed.scrap.reactive_scope.ReactiveFuncNode(func: ~typing.Callable, name: str | None = None, bind: dict = <factory>, out: str | None = None, func_label: str | None = None, names_maker: ~typing.Callable = <function underscore_func_node_names_maker>, node_validator: ~typing.Callable = <function basic_node_validator>)[source]
A
FuncNode
that computes on a scope only if the scope has what it takes- call_on_scope(scope, write_output_into_scope=True)[source]
Call the function using the given scope both to source arguments and write results.
Note: This method is only meant to be used as a backend to __call__, not as an actual interface method. Additional control/constraints on read and writes can be implemented by providing a custom scope for that.
- class meshed.scrap.reactive_scope.ReactiveScope(func_nodes=(), scope_factory=<class 'dict'>)[source]
A scope that reacts to writes by computing associated functions, themselves writing in the scope, creating a chain reaction that propagates information through the scope.
- Parameters:
func_nodes (Iterable[ReactiveFuncNode]) – The functions that will be called when the scope is written to.
scope_factory (Callable[[], MutableMapping]) – A factory that returns a new scope. The scope will be cleared by calling this factory at each call to .clear().
Examples
First, we need some func nodes to define the reaction relationships. We’ll stuff these func nodes in a DAG, for ease of use, but it’s not necessary.
>>> from meshed import FuncNode, DAG >>> >>> def f(a, b): ... return a + b >>> def g(a_plus_b, d): ... return a_plus_b * d >>> f_node = FuncNode(func=f, out='a_plus_b') >>> g_node = FuncNode(func=g, bind={'d': 'b'}) >>> d = DAG((f_node, g_node)) >>> >>> print(d.dot_digraph_ascii()) a │ │ ▼ ┌────────┐ b ──▶ │ f │ └────────┘ │ │ │ │ │ ▼ │ │ a_plus_b │ │ │ │ │ │ ▼ │ ┌────────┐ └─────▶ │ g_ │ └────────┘ │ │ ▼ g
Now we make a scope with these func nodes.
>>> s = ReactiveScope(d)
The scope starts empty (by default).
>>> s <ReactiveScope with .scope: {}>
So if we try to access any key, we’ll get a KeyError.
>>> s['g'] Traceback (most recent call last): ... KeyError: 'g'
That’s because we didn’t put write anything in the scope yet.
But, if you give
g_
enough data to be able to computeg
(namely, if you write values ofb
anda_plus_b
), theng
will automatically be computed.>>> s['b'] = 3 >>> s['a_plus_b'] = 5 >>> s <ReactiveScope with .scope: {'b': 3, 'a_plus_b': 5, 'g': 15}>
So now we can access
g
.>>> s['g'] 15
Note though, that we first showed that
g
appeared in the scope before we explicitly asked for it. This was to show thatg
was computed as a side-effect of writing to the scope, not because we asked for it, triggering the computationLet’s clear the scope and show that by specifying
a
andb
, we get all the other values of the network.>>> s.clear() >>> s <ReactiveScope with .scope: {}> >>> s['a'] = 3 >>> s['b'] = 4 >>> s <ReactiveScope with .scope: {'a': 3, 'b': 4, 'a_plus_b': 7, 'g': 28}> >>> s['g'] # (3 + 4) * 4 == 7 * 4 == 28 28