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

Case study — Explainer

explainer_lift is a ShapeLift constructor that wraps a fold with per-node trace recording. It’s a useful case study because it changes H and R (not N), composes as a post-lift, and produces a result type that lets callers inspect the full computation tree.

What it does

#![allow(unused)]
fn main() {
    pub fn explainer_lift<N, H, R>()
        -> ShapeLift<Shared, N, H, R,
                     N,
                     ExplainerHeap<N, H, ExplainerResult<N, H, R>>,
                     ExplainerResult<N, H, R>>
    where N: Clone + Send + Sync + 'static,
          H: Clone + Send + Sync + 'static,
          R: Clone + Send + Sync + 'static,
    {
        let fold_xform: <Shared as ShapeCapable<N>>::FoldXform<
            H, R, N,
            ExplainerHeap<N, H, ExplainerResult<N, H, R>>,
            ExplainerResult<N, H, R>,
        > = Arc::new(move |f: Fold<N, H, R>| {
            let f1 = f.clone();
            let f2 = f.clone();
            let f3 = f;
            sfold::fold(
                move |n: &N| ExplainerHeap::new(n.clone(), f1.init(n)),
                move |heap: &mut ExplainerHeap<N, H, ExplainerResult<N, H, R>>,
                      child: &ExplainerResult<N, H, R>| {
                    f2.accumulate(&mut heap.working_heap, &child.orig_result);
                    heap.transitions.push(ExplainerStep {
                        incoming_result: child.clone(),
                        resulting_heap:  heap.working_heap.clone(),
                    });
                },
                move |heap: &ExplainerHeap<N, H, ExplainerResult<N, H, R>>| ExplainerResult {
                    orig_result: f3.finalize(&heap.working_heap),
                    heap:        heap.clone(),
                },
            )
        });
        ShapeLift::new(
            <Shared as ShapeCapable<N>>::identity_treeish_xform(),
            fold_xform,
        )
    }
}

The lift wraps:

  • H becomes ExplainerHeap<N, H, ExplainerResult<N, H, R>>: the original H plus a vector of per-child transitions recorded during accumulate.
  • R becomes ExplainerResult<N, H, R>: the original result plus the full heap (so callers can walk the trace tree).

Every node’s finalize produces both the original R and the recorded history.

Usage

Via the sugar method .explain() on any Stage-2 pipeline — a treeish-rooted Stage2Pipeline, a seed-rooted Stage2Pipeline, or a TreeishPipeline via auto-lift. A SeedPipeline requires an explicit .lift() first:

#![allow(unused)]
fn main() {
    #[test]
    fn explainer_usage() {
        use hylic_pipeline::prelude::*;

        #[derive(Clone)]
        struct N { val: u64, children: Vec<N> }

        let f: Fold<N, u64, u64> = fold(
            |n: &N| n.val,
            |h: &mut u64, c: &u64| *h += c,
            |h: &u64| *h,
        );
        let root = N { val: 1, children: vec![N { val: 2, children: vec![] }] };

        let trace: ExplainerResult<N, u64, u64> =
            TreeishPipeline::new(treeish(|n: &N| n.children.clone()), &f)
                .lift()
                .then_lift(Shared::explainer_lift::<N, u64, u64>())
                .run_from_node(&FUSED, &root);
        assert_eq!(trace.orig_result, 3);
    }
}

The return type is ExplainerResult<N', H, R> where N' is the chain’s current node type — N on a treeish-rooted chain, but SeedNode<N> on a seed-rooted chain (since the seed chain’s node type is SeedNode<N> from .lift() onward). Access .orig_result for the original computation’s output:

#![allow(unused)]
fn main() {
    #[test]
    fn explainer_orig_result() {
        use hylic_pipeline::prelude::*;

        #[derive(Clone)]
        struct Node { v: u64, ch: Vec<Node> }
        let root = Node { v: 3, ch: vec![
            Node { v: 2, ch: vec![] },
            Node { v: 1, ch: vec![] },
        ]};

        let tp: TreeishPipeline<Shared, Node, u64, u64> = TreeishPipeline::new(
            treeish(|n: &Node| n.ch.clone()),
            &fold(|n: &Node| n.v, |h: &mut u64, c: &u64| *h += c, |h: &u64| *h),
        );

        let trace: ExplainerResult<Node, u64, u64> = tp
            .explain()
            .run_from_node(&FUSED, &root);
        // Sum = 3 + 2 + 1 = 6.
        assert_eq!(trace.orig_result, 6);
        // Every non-leaf records its child-accumulations.
        assert!(!trace.heap.transitions.is_empty());
    }
}

Sealed view on the seed path

For an N-typed view of the trace that hides SeedNode entirely, project via the standard From conversion:

use hylic::prelude::SeedExplainerResult;

let raw: ExplainerResult<SeedNode<N>, H, R> =
    pipeline.lift().explain().run_from_slice(&FUSED, &seeds, h0);
let sealed: SeedExplainerResult<N, H, R> = raw.into();

// sealed.entry_initial_heap, entry_working_heap, orig_result — EntryRoot row promoted out
// sealed.roots: Vec<ExplainerResult<N, H, R>>                — per-seed subtrees

Use raw when you need to keep composing lifts on top of .explain() (the chain type is what matters); use sealed when you want an N-typed view for formatting or assertions — the library’s invariant guarantees every below-root node is a Node(n), so the unwrap is total.

Composing with other lifts

Because explain() is just a then_lift(Shared::explainer_lift()), it composes:

let r = pipeline
    .wrap_init(|n, orig| orig(n) * 2)   // first lift
    .explain()                           // records the wrap_init results
    .zipmap(|r| r.orig_result > 100);    // inspect .orig_result

Order matters: lifts run bottom-up (the first .wrap_init runs innermost; .explain sees its results; .zipmap sees the ExplainerResult).

Streaming variant

Shared::explainer_describe_lift(fmt, emit) emits formatted trace lines per node via a callback and leaves MapR = R unchanged:

use hylic::prelude::*;
let _ = Shared::explainer_describe_lift::<Node, u64, u64, _, _>(
    trace_fold_compact::<Node, u64, u64>,
    |line: &str| eprintln!("[trace] {line}"),
);

Local mirror deferred (blocked on Send+Sync in the formatter); explainer_lift is available for Local.