prismqueer 0.1.0

The spectral-triple substrate — five operations (focus, project, split, shift, settle), the Prism trait, zero deps. The foundation.
Documentation
//! Prism — focus | project | settle.
//!
//! A [`Beam`] carries three things through a pipeline: a value, the input
//! that produced it, and the accumulated loss ([`Imperfect`]). A [`Prism`]
//! defines three operations over beams:
//!
//! - **focus** — select what matters from the input.
//! - **project** — transform the focused value (precision cut, eigenvalue
//!   threshold, the lossy step where information may not survive).
//! - **settle** — produce the output from what survived projection.
//!
//! ```ignore
//! let result = seed("hello")
//!     .apply(Focus(&my_prism))
//!     .apply(Project(&my_prism))
//!     .apply(Settle(&my_prism));
//! ```
//!
//! Algebraically: a Beam is a **semifunctor** — you can map over the carried
//! value (`smap`), but the identity law may not hold because Failure beams
//! break it (mapping over a Failure panics rather than returning the same
//! Failure). A Prism is a **monoid** lifted into that semifunctor: prisms
//! compose associatively (`focus | project | settle` chains), and an identity
//! prism exists (pass-through on all three stages). This means pipelines are
//! type-safe by construction — the compiler enforces that each stage's output
//! type matches the next stage's input type.

// Allow prism-derive proc macros to reference `prismqueer::` paths
// when used within this crate.
extern crate self as prismqueer;

pub mod beam;
pub mod coincidence;
pub mod crystal;
pub mod luminosity;
pub mod scalar_loss;
pub mod substrate_ref;
pub mod trace;

pub mod connection;
pub mod content;
pub mod kernel;
pub mod merkle;
pub mod metal;
pub mod named;
pub mod oid;
pub mod optic_kind;
pub mod precision;
pub mod spectral_oid;
pub mod spectral_uuid;
pub mod store;

#[cfg(feature = "optics")]
pub mod optics;

#[cfg(feature = "pq")]
pub mod pq;

#[cfg(feature = "bundle")]
pub mod bundle;

#[cfg(feature = "lambda")]
pub mod lambda;

#[cfg(feature = "lapack")]
pub mod ffi;

#[cfg(feature = "lapack")]
pub mod spectral_dimension;

#[cfg(feature = "bundle")]
pub use bundle::{
    Bundle, Closure, Connection, Cyclic, Fiber, Gauge, GroupStructure, IdentityPrism,
    LawvereFixedPoint, StableFiber, Transport,
};

pub use beam::{Beam, Operation, Optic};
pub use coincidence::{canonical_hash, coincidence_hash, Detector, HashPrism};
/// Re-export the `declaration!{}` function-like proc-macro — the
/// `@code/rust/macro.shim_type` reception entry point (T23, per
/// `mirror/shards/code/rust/macro.mirror` and
/// `mirror/docs/specs/code-macro-surface.md`). Reads a substrate
/// `type` declaration as input tokens and emits the Rust
/// struct/enum that realises it.
pub use prismqueer_projections::declaration;
/// Re-export `#[derive(Lambda)]` for named lambda phases.
#[cfg(feature = "lambda")]
pub use prismqueer_projections::Lambda as DeriveLambda;
/// Re-export the `#[derive(Prism)]` proc macro.
///
/// The derive macro and the `Prism` trait live in different namespaces:
/// `#[derive(Prism)]` invokes the proc macro. `impl Prism for X` uses the trait.
/// Rust resolves these without collision. The `DerivePrism` alias is provided
/// for explicit disambiguation when needed.
pub use prismqueer_projections::Prism as DerivePrism;
pub use scalar_loss::ScalarLoss;
pub use substrate_ref::Ref;
pub use terni::{Diagnostic, Imperfect, Loss, Metric, PropertyVerdict, Transparency};
pub use trace::{Op, Step, StepOutput, Trace, Traced};

pub use connection::{Carrier, ScalarConnection};
pub use content::ContentAddressed;
pub use crystal::Crystal;
pub use kernel::{Decomposition, KernelSpec};
pub use luminosity::Luminosity;
pub use merkle::{diff, Delta, MerkleTree};
pub use named::Named;
pub use oid::{Addressable, Oid};
pub use optic_kind::{FieldOptic, OpticKind};
pub use precision::{Precision, Pressure};
pub use spectral_oid::SpectralOid;
pub use spectral_uuid::{ParseError as SpectralUuidParseError, SpectralUuid};
pub use store::Store;

// ---------------------------------------------------------------------------
// Prism trait
// ---------------------------------------------------------------------------

/// Three optic operations over beams: focus, project, settle.
///
/// The associated types form a chain: `Input` feeds `focus`, whose output
/// beam (`Focused`) feeds `project`, whose output beam (`Projected`) feeds
/// `settle`, producing `Refracted`. The chain is enforced by the `Beam::In`
/// constraints — each stage's `In` must equal the previous stage's `Out`.
/// This makes type mismatches between pipeline stages a compile error.
pub trait Prism {
    type Input: Beam;
    type Focused: Beam<In = <Self::Input as Beam>::Out>;
    type Projected: Beam<In = <Self::Focused as Beam>::Out>;
    type Refracted: Beam<In = <Self::Projected as Beam>::Out>;

    fn focus(&self, beam: Self::Input) -> Self::Focused;
    fn project(&self, beam: Self::Focused) -> Self::Projected;
    fn settle(&self, beam: Self::Projected) -> Self::Refracted;
}

/// Blanket impl: `&P` is a Prism wherever `P` is.
impl<P: Prism> Prism for &P {
    type Input = P::Input;
    type Focused = P::Focused;
    type Projected = P::Projected;
    type Refracted = P::Refracted;

    fn focus(&self, beam: P::Input) -> P::Focused {
        P::focus(self, beam)
    }
    fn project(&self, beam: P::Focused) -> P::Projected {
        P::project(self, beam)
    }
    fn settle(&self, beam: P::Projected) -> P::Refracted {
        P::settle(self, beam)
    }
}

/// Run a prism end-to-end: focus, then project, then settle.
///
/// Equivalent to the DSL pattern `beam.apply(Focus(p)).apply(Project(p)).apply(Settle(p))`
/// but without requiring the caller to spell out each stage.
pub fn apply<P: Prism>(prism: &P, beam: P::Input) -> P::Refracted {
    beam.apply(Focus(prism))
        .apply(Project(prism))
        .apply(Settle(prism))
}

/// Run a prism end-to-end on a *state value* (not a beam) and discard
/// the source dimension, returning the focus of the refracted beam as
/// an [`Imperfect`].
///
/// **Heterogeneous.** The input state's type (`SIn`) and the output
/// state's type (`Out`) are independent — they need not coincide. This
/// is the algebra-action shape: an element `P` of the algebra `A` acts
/// on a state of type `SIn` and produces a state of type `Out`, with
/// any accumulated residual ([`Loss`]) and any failure (`Error`)
/// carried in the returned [`Imperfect`].
///
/// The seed beam is constructed internally as `Optic::ok((), state)`.
/// Callers that already have a `P::Input` beam should use [`apply`]
/// instead. The constraints bind:
/// - `P::Input = Optic<(), SIn>` — the standard "seed" shape (source
///   position is `()`, value position carries the input state),
/// - `P::Refracted = Optic<In, Out, E, L>` — the final beam carries
///   an output state of type `Out`, with arbitrary error and loss
///   types `E` and `L`. The `In` parameter holds the previous stage's
///   output type and is dropped by `into_focus`.
///
/// Returns `Imperfect<Out, E, L>` — the focus payload of the refracted
/// beam: `Success(out)`, `Partial(out, loss)`, or `Failure(err, loss)`.
///
/// ## Why this exists (and isn't just `apply(...).into_focus()`)
///
/// The combination "seed the input + drop the source on the way out"
/// is the canonical operator-action pattern throughout the mirror
/// bootstrap and any future algebra-as-data consumer. Naming it
/// `apply_h` (operator action on `H`) ties the implementation to its
/// spectral-triple semantics and removes the need for every consumer
/// to reinvent the heterogeneous wrapper — which, in practice, drifts
/// toward over-constrained shapes (e.g. forcing `SIn = Out`) and then
/// has to be bypassed when a heterogeneous parser/serializer arrives.
pub fn apply_h<P, SIn, In, Out, E, L>(prism: &P, state: SIn) -> Imperfect<Out, E, L>
where
    P: Prism<Input = Optic<(), SIn>, Refracted = Optic<In, Out, E, L>>,
    L: Loss,
{
    apply(prism, Optic::ok((), state)).into_focus()
}

// ---------------------------------------------------------------------------
// Operation structs — three pipeline stages
// ---------------------------------------------------------------------------

/// focus: Input → Focused.
pub struct Focus<P>(pub P);

/// project: Focused → Projected.
pub struct Project<P>(pub P);

/// settle: Projected → Refracted.
pub struct Settle<P>(pub P);

impl<P: Prism> Operation<P::Input> for Focus<P> {
    type Output = P::Focused;
    fn op(&self) -> Op {
        Op::Focus
    }
    fn apply(self, beam: P::Input) -> P::Focused {
        self.0.focus(beam)
    }
}

impl<P: Prism> Operation<P::Focused> for Project<P> {
    type Output = P::Projected;
    fn op(&self) -> Op {
        Op::Project
    }
    fn apply(self, beam: P::Focused) -> P::Projected {
        self.0.project(beam)
    }
}

impl<P: Prism> Operation<P::Projected> for Settle<P> {
    type Output = P::Refracted;
    fn op(&self) -> Op {
        Op::Settle
    }
    fn apply(self, beam: P::Projected) -> P::Refracted {
        self.0.settle(beam)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use terni::Imperfect;

    /// A prism that counts characters.
    /// focus: String → Vec<char>, project: Vec<char> → usize, settle: usize → String
    struct CountPrism;

    impl Prism for CountPrism {
        type Input = Optic<(), String>;
        type Focused = Optic<String, Vec<char>>;
        type Projected = Optic<Vec<char>, usize>;
        type Refracted = Optic<usize, String>;

        fn focus(&self, beam: Self::Input) -> Self::Focused {
            let chars: Vec<char> = beam
                .result()
                .ok()
                .expect("focus: Err beam")
                .chars()
                .collect();
            beam.next(chars)
        }

        fn project(&self, beam: Self::Focused) -> Self::Projected {
            let n = beam.result().ok().expect("project: Err beam").len();
            beam.next(n)
        }

        fn settle(&self, beam: Self::Projected) -> Self::Refracted {
            let n = *beam.result().ok().expect("settle: Err beam");
            beam.next(format!("{} chars", n))
        }
    }

    fn seed(s: &str) -> Optic<(), String> {
        Optic::ok((), s.to_string())
    }

    // --- Prism method tests ---

    #[test]
    fn focus_yields_chars() {
        let b = CountPrism.focus(seed("hello"));
        assert_eq!(b.result().ok(), Some(&vec!['h', 'e', 'l', 'l', 'o']));
        assert_eq!(b.input(), &"hello".to_string());
    }

    #[test]
    fn project_yields_count() {
        let f = CountPrism.focus(seed("hello"));
        let p = CountPrism.project(f);
        assert_eq!(p.result().ok(), Some(&5));
    }

    #[test]
    fn refract_produces_string() {
        let f = CountPrism.focus(seed("hi"));
        let p = CountPrism.project(f);
        let r = CountPrism.settle(p);
        assert_eq!(r.result().ok(), Some(&"2 chars".to_string()));
    }

    // --- Operation tests ---

    #[test]
    fn operation_focus() {
        let b = Focus(&CountPrism).apply(seed("hello"));
        assert_eq!(b.result().ok(), Some(&vec!['h', 'e', 'l', 'l', 'o']));
    }

    #[test]
    fn operation_project() {
        let focused = CountPrism.focus(seed("hello"));
        let p = Project(&CountPrism).apply(focused);
        assert_eq!(p.result().ok(), Some(&5));
    }

    #[test]
    fn operation_refract() {
        let projected = seed("hi")
            .apply(Focus(&CountPrism))
            .apply(Project(&CountPrism));
        let r = Settle(&CountPrism).apply(projected);
        assert_eq!(r.result().ok(), Some(&"2 chars".to_string()));
    }

    // --- DSL pipeline ---

    #[test]
    fn dsl_pipeline() {
        let r = seed("hi")
            .apply(Focus(&CountPrism))
            .apply(Project(&CountPrism))
            .apply(Settle(&CountPrism));
        assert!(r.is_ok());
        assert_eq!(r.result().ok(), Some(&"2 chars".to_string()));
    }

    #[test]
    fn apply_fn_end_to_end() {
        let r = apply(&CountPrism, seed("hi"));
        assert!(r.is_ok());
        assert_eq!(r.result().ok(), Some(&"2 chars".to_string()));
    }

    // --- Blanket impl ---

    #[test]
    fn ref_prism_works() {
        let prism = CountPrism;
        let r = apply(&prism, seed("abc"));
        assert_eq!(r.result().ok(), Some(&"3 chars".to_string()));
    }

    // --- Op labels ---

    #[test]
    fn operation_op_labels() {
        assert_eq!(Focus(&CountPrism).op(), Op::Focus);
        assert_eq!(Project(&CountPrism).op(), Op::Project);
        assert_eq!(Settle(&CountPrism).op(), Op::Settle);
    }

    // --- smap in user space (split/zoom equivalent) ---

    #[test]
    fn smap_as_zoom() {
        let projected = seed("hello")
            .apply(Focus(&CountPrism))
            .apply(Project(&CountPrism));
        let zoomed = projected.smap(|&n| Imperfect::success(n * 2));
        assert_eq!(zoomed.result().ok(), Some(&10));
    }

    #[test]
    fn smap_as_split() {
        let projected = seed("abc")
            .apply(Focus(&CountPrism))
            .apply(Project(&CountPrism));
        let split = projected.smap(|&n| Imperfect::success((0..n as u32).collect::<Vec<_>>()));
        assert_eq!(split.result().ok(), Some(&vec![0, 1, 2]));
    }
}