Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sugars — the chainable surface

Every transform users reach for at Stage 1 or Stage 2 is a trait method. Each method picks an axis, builds the right library lift, and either reshapes the Stage-1 slots in place (Stage 1) or appends the lift to the chain via then_lift (Stage 2).

Trait surfaces

Where you areSugars in scope
SeedPipeline<Shared, …>SeedSugarsShared
SeedPipeline<Local, …>SeedSugarsLocal
TreeishPipeline<Shared, …>TreeishSugarsShared
TreeishPipeline<Local, …>TreeishSugarsLocal
Stage2Pipeline<Base, L> (Shared, any Base)Stage2SugarsShared
Stage2Pipeline<Base, L> (Local, any Base)Stage2SugarsLocal

use hylic_pipeline::prelude::*; brings them all in scope.

Shared and Local: same names, different bounds

Method names are identical across domains. Only the closure storage and bounds differ:

// Shared: parallel-safe; closures must be Send + Sync.
let r = shared_pipe.wrap_init(w).zipmap(m).run(...);

// Local: same call shape, captures may be non-Send.
let r = local_pipe.wrap_init(w).zipmap(m).run(...);

Stage 2: one trait covers both Bases

Stage2SugarsShared is one trait, blanket-implemented on every Stage2Pipeline<Base, L>. The treeish-rooted vs seed-rooted dispatch happens inside the lift-construction call, not at the trait level. Every Stage-2 sugar body is one line:

#![allow(unused)]
fn main() {
    fn wrap_init<W>(self, w: W) -> Self::With<ShapeLift<Shared,
        <<Self::Base as Stage2Base>::Wrap as Wrap>::Of<UN>, H, R,
        <<Self::Base as Stage2Base>::Wrap as Wrap>::Of<UN>, H, R>>
    where
        <Self::Base as Stage2Base>::Wrap: WrapShared,
        <<Self::Base as Stage2Base>::Wrap as Wrap>::Of<UN>: Clone + Send + Sync + 'static,
        W: Fn(&UN, &dyn Fn(&UN) -> H) -> H + Send + Sync + 'static,
    {
        self.then_lift(<<Self::Base as Stage2Base>::Wrap as WrapShared>::build_wrap_init::<UN, H, R, _>(w))
    }
}

<<Self::Base as Stage2Base>::Wrap as WrapShared>::build_wrap_init is the dispatch. Identity (treeish-rooted) calls Shared::wrap_init_lift directly; SeedWrap (seed-rooted) wraps the user’s closure with a SeedNode::Node(_)-peeling adapter, then calls the same Shared::wrap_init_lift. Both produce a ShapeLift; both forward to then_lift. From the user’s perspective the closure types at &UN either way. See Wrap dispatch for the full mechanics.

Stage 1: per-Base reshape sugars

Stage-1 reshape rewrites the base slots in place and returns a fresh Stage-1 pipeline of (possibly different) type parameters:

#![allow(unused)]
fn main() {
pub trait SeedSugarsShared<N, Seed, H, R>: Sized
where N: Clone + 'static, Seed: Clone + 'static,
      H: Clone + 'static, R: Clone + 'static,
{
    fn filter_seeds<P>(self, pred: P) -> SeedPipeline<Shared, N, Seed, H, R>
    where P: Fn(&Seed) -> bool + Send + Sync + 'static;

    fn wrap_grow<W>(self, wrapper: W) -> SeedPipeline<Shared, N, Seed, H, R>
    where W: Fn(&Seed, &dyn Fn(&Seed) -> N) -> N + Send + Sync + 'static;

    fn map_node_bi<N2, Co, Contra>(self, co: Co, contra: Contra)
        -> SeedPipeline<Shared, N2, Seed, H, R>
    where N2: Clone + 'static,
          Co:     Fn(&N) -> N2 + Send + Sync + 'static,
          Contra: Fn(&N2) -> N + Send + Sync + 'static;

    fn map_seed_bi<Seed2, ToNew, FromNew>(self, to_new: ToNew, from_new: FromNew)
        -> SeedPipeline<Shared, N, Seed2, H, R>
    where Seed2: Clone + 'static,
          ToNew:   Fn(&Seed) -> Seed2 + Send + Sync + 'static,
          FromNew: Fn(&Seed2) -> Seed + Send + Sync + 'static;
}

}

Stage-2 sugars are not in scope until .lift() (or the TreeishPipeline auto-lift) has produced a Stage2Pipeline.

Catalogue

Stage 1 — SeedSugarsShared / SeedSugarsLocal

Operates on SeedPipeline<D, N, Seed, H, R>:

methodoutput shape
filter_seeds(pred)SeedPipeline<D, N, Seed, H, R>
wrap_grow(w)SeedPipeline<D, N, Seed, H, R>
map_node_bi(co, contra)SeedPipeline<D, N2, Seed, H, R>
map_seed_bi(to, from)SeedPipeline<D, N, Seed2, H, R>

Stage 1 — TreeishSugarsShared / TreeishSugarsLocal

Operates on TreeishPipeline<D, N, H, R>:

methodoutput shape
map_node_bi(co, contra)TreeishPipeline<D, N2, H, R>

Stage 2 — Stage2SugarsShared / Stage2SugarsLocal

Operates on Stage2Pipeline<Base, L> (and on TreeishPipeline via auto-lift). User closures type at &UN; the chain’s actual N is UN (treeish-rooted) or SeedNode<UN> (seed-rooted), bridged by Wrap.

methodwhat the lift does
wrap_init(w)intercept init at every node
wrap_accumulate(w)intercept accumulate
wrap_finalize(w)intercept finalize
zipmap(m)extend R: R → (R, Extra)
map_r_bi(fwd, bwd)change R bijectively
filter_edges(pred)drop edges from the graph
wrap_visit(w)intercept graph visit
memoize_by(key)memoise subtree results by key
map_n_bi(co, contra)change N bijectively (chain-tip)
explain()wrap fold with per-node trace recording
explain_describe(fmt, emit)streaming trace; chain-tip R unchanged (Shared only)

The Stage-1 reshape map_node_bi and the Stage-2 sugar map_n_bi share a purpose (change N) but are distinct operations. Stage 1 rewrites the base slots in place; Stage 2 composes a ShapeLift onto the chain. Use Stage 2 when the N change must sit on top of earlier sugars.

Where wrap_init’s second argument comes from

Every wrap_* user closure receives an orig: &dyn Fn(...) -> ... parameter alongside the node. orig is the prior fold’s corresponding phase, exposed as a value so the sugar body can compose with it: |n, orig| orig(n) + 1. Lifts are, at the type level, natural transformations between fold algebras; a phase mapper takes the prior phase as input and produces the new phase. See the type-level deep dive.