Skip to content

Bind pairwise() in wingfoil-python #882

Description

@0-jake-0

Follow-up to #879 / #800. pairwise() landed in the Rust catalog and reaches all three engines, but has no Python binding. Step 7 of /new-op treats the binding as part of adding an op, and both of this op's nearest mirrors already have one: difference (graph.rs:449, python.rs:205, plus a README table row) and skip (graph.rs:431).

What: stream.pairwise() on the Python side, emitting a 2-tuple of the previous and current values.

Why #[pyop] cannot do this one

Everything Python-composable rides one erased edge type, PyElement. Pairwise's Out is (T, T), so the erased form would be (PyElement, PyElement) — not a PyElement, and the proc macro has no way to wrap it. So this is a hand-written PyStream method, not an attribute on the Op impl.

That is fine, and the shape already exists in the file: PyStream::split / item (crates/wingfoil-python/src/graph.rs:803) treat values as indexable Python objects, and split_decomposes_tuples pins that contract. Emitting a PyTuple as the element means stream.pairwise().split() composes for free.

Shape

Stateful single input, so it wants the stateful sibling of wire_stateless (see how drop_small_change at graph.rs:372 carries state) — Option<PyElement> for the previous value, Tick::Quiet until it is filled:

Ok(Tick::Value(PyElement::new(
    PyTuple::new(py, [prev, cur])?.unbind().into(),
)))

Acceptance

  • PyStream::pairwise in crates/wingfoil-python/src/graph.rs, next to difference
  • Stream::pairwise forwarder in crates/wingfoil-python/src/python.rs
  • A Rust seam test beside difference_of_counter_is_one (graph.rs:1409), covering the quiet first tick and a non-arithmetic element type — that is the capability difference cannot offer and the reason the op exists
  • A pytest in crates/wingfoil-python/tests/, including pairwise().split() round-tripping into two streams
  • Row in the combinator table in crates/wingfoil-python/README.md (next to .difference())

Getting started

/bind-adapter is for adapters; this one follows step 7 of .claude/commands/new-op.md. Background: docs/python-interop.md. Build and test with cd crates/wingfoil-python && maturin develop && pytest, plus cargo test -p wingfoil-python for the seam tests. Branch from main, PR base main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgood first issueGood for newcomerspythonPython bindings (wingfoil-python)size: smallSmall effort (< 1 day)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions