Their edge is an ordered list of markers; ours is a typed edge with subclasses. Theirs composes, ours does not.
# theirs — measured against 2.35.1, runs and returns ['ADA','GRACE']
g.edge_from(g.start_node).map().transform(lambda ctx: ctx.inputs["name"]).to(shout)
# ours — not expressible. MapEdgeSpec and TransformEdgeSpec are separate types,
# and no edge is both.
What it would look like
EdgeSpec(source=a, target=b, carries=papers,
path=[Map(delivers=paper), Transform(delivers=pmid, apply=take_pmid)])
One EdgeSpec, no subclasses, and delivers becomes the output of the last item.
Three costs
edges stops being flat. Today it is a tuple of arrows you scan in one pass; this nests a list inside a tuple, in the most-read part of a design.
check_variables becomes a small interpreter. It would thread a type through each item rather than comparing two ends. Doable — that is what their type parameter does — and it would let intermediate steps be checked, but it is no longer a two-line check.
- Bindable identity per item. A
Transform item that a strategy binds needs its own identity for varies() and check_bindings, so path items become a second kind of bindable thing alongside nodes and transform edges.
And an argument for what we have
Their Path is partly an artefact of the fluent builder — the field is literally called working_items, an accumulator left behind by method chaining. Our declaration is a literal, and a list is the natural shape of the former, not the latter.
More substantially: MapEdgeSpec becomes a real node at build time and TransformEdgeSpec does not.
# _flatten_paths, graph_builder.py
assert not isinstance(item, MapMarker | BroadcastMarker), 'These should be removed during Graph building'
Map and broadcast are rewritten into Fork nodes before the executor ever runs; transform and label survive in the Path and are walked per completion. So the two are not the same kind of thing, and our separate types say so where a uniform list hides it until _flatten_paths.
Status
No caller. Nothing in eleven ladder rungs or in nobsmed's six arms needs map-then-transform on one wire.
Trigger to build it: the first design that wants to fan out a collection and reshape each item before it lands — e.g. carries=list[Paper], delivers=pmid — where the alternative is a step that exists only to unwrap.
parity.py now records the composition gap; the table was feature-by-feature and read as though map ✓ + transform ✓ meant both together.
Their edge is an ordered list of markers; ours is a typed edge with subclasses. Theirs composes, ours does not.
What it would look like
One
EdgeSpec, no subclasses, anddeliversbecomes the output of the last item.Three costs
edgesstops being flat. Today it is a tuple of arrows you scan in one pass; this nests a list inside a tuple, in the most-read part of a design.check_variablesbecomes a small interpreter. It would thread a type through each item rather than comparing two ends. Doable — that is what their type parameter does — and it would let intermediate steps be checked, but it is no longer a two-line check.Transformitem that a strategy binds needs its own identity forvaries()andcheck_bindings, so path items become a second kind of bindable thing alongside nodes and transform edges.And an argument for what we have
Their
Pathis partly an artefact of the fluent builder — the field is literally calledworking_items, an accumulator left behind by method chaining. Our declaration is a literal, and a list is the natural shape of the former, not the latter.More substantially:
MapEdgeSpecbecomes a real node at build time andTransformEdgeSpecdoes not.Map and broadcast are rewritten into
Forknodes before the executor ever runs; transform and label survive in thePathand are walked per completion. So the two are not the same kind of thing, and our separate types say so where a uniform list hides it until_flatten_paths.Status
No caller. Nothing in eleven ladder rungs or in nobsmed's six arms needs map-then-transform on one wire.
Trigger to build it: the first design that wants to fan out a collection and reshape each item before it lands — e.g.
carries=list[Paper],delivers=pmid— where the alternative is a step that exists only to unwrap.parity.pynow records the composition gap; the table was feature-by-feature and read as thoughmap✓ +transform✓ meant both together.