Skip to content

CU5 — The DSL

from somatize import Filter, Graph
g = Graph.somatize(Source() >> (Left().named("left") | Right().named("right")) >> Mean())
g.forward(0)
let (graph, catalog) = (filter("source", Add(1.0))
>> (filter("left", Add(10.0)) | filter("right", Add(100.0)))
>> filter("join", Mean))
.somatize()?;

Status: closed. Same tests, plus build.rs and test_dsl.py.

>> chains, | opens branches, and a >> after some open branches closes them — which is CU4’s fan-in: the node on the right receives the map. The expression above is the diamond.

  1. The same syntax in both languages. In Rust it falls out of implementing std::ops::Shr and BitOr on a type of our own; no macro needed (macro_rules! would give syntax the operators do not, but that is not needed here). The precedence matches too: >> binds tighter than |, so the branches go in parentheses in both.
  2. It is called somatize, which is the project’s verb. In Python it is a classmethod of Graph; in Rust, a method of Wire, because it returns two things — the structure and the store — and neither contains the other.
  3. There is a Python class on top of the Rust one. somatize walks an expression of Python objects, so it cannot be in Rust; and a #[pyclass] is an immutable type, so it cannot be hung on it at import time either. It is declared in a subclass body, which is also the only thing help(), an IDE and mypy can see. It is the same structure as the original Soma (soma/_graph.py), and for the same reasons.
  4. The base class is abstract, and inheritance is what decides. It requires its method with @abstractmethod, so a subclass without forward cannot even be instantiated, and isinstance is the only question the DSL asks. It was a correction: the bases were born as empty mixins that asked by duck typing whether the object had a method, and an object could get three different answers depending on which door it came in through. The names promised a contract nobody enforced. node() stays the lower door and accepts an outside object that inherits from nothing, because there the type is the caller’s to choose.
  5. Wire does not materialize until somatize. It records where you enter and where you leave, plus the lists of nodes and edges. That way joining two pieces is concatenating lists rather than merging two graphs, and a repeated id is caught at the end, once.
  6. The DSL is nothing but node and edge. There is a test that builds the same graph both ways and compares nodes, edges and plan.