๐Ÿฆ€ Functional Rust

250: Grand Synthesis

Difficulty: Expert Level: Synthesis All category theory concepts from this library โ€” sum types, product types, Curry-Howard, profunctors, comonads, applicatives, limits/colimits โ€” woven together into one validated data pipeline.

The Problem This Solves

Every individual concept in this library solves a specific problem. But real software needs them all at once. A validation pipeline needs sum types for errors, product types for valid data, Curry-Howard for type-safe invariants, profunctors for transforming pipeline stages, comonads for context-aware processing, applicatives for accumulating all errors instead of stopping at the first, and limits/colimits to model how data flows and combines. This capstone shows that the categories aren't separate techniques โ€” they're facets of one coherent design philosophy. When you internalize all of them, you stop writing ad hoc validation code and start composing principled, type-safe transformations that the compiler verifies for you. The pipeline validates raw user input (`RawInput`) through layered stages: parse โ†’ validate โ†’ enrich โ†’ format. Each stage uses the right categorical tool for its job.

The Intuition

Think of the pipeline as a factory floor: Stage 1 โ€” Sum + Product types: The data model. `ValidationError` is a sum type (different error kinds). `ValidatedUser` is a product type (all fields present and valid). These two constructions encode all possible states of the data. Stage 2 โ€” Curry-Howard: Each validation rule is a proof. `fn validate_name(s: &str) -> Result<ValidatedName, ValidationError>` is a proof that "given a string, either I can prove it's a valid name, or I can prove it's invalid." The types ARE the specification. Stage 3 โ€” Validated applicative: Unlike `Result` (which stops at the first error), `Validated` accumulates all errors. This uses the applicative structure โ€” combining independent validation results. Applicative = Day convolution specialized to the `Validated` functor. All fields are validated in parallel; all errors collected. Stage 4 โ€” Profunctor: The `Mapper<A, B>` profunctor adapts pipeline stages. A transformation that works on `Name` can be lifted to work on `User` via `dimap`. This is the lens structure: focus on a part, transform it, reconstruct the whole. Stage 5 โ€” Comonad (Env): The `Env` comonad carries configuration alongside the data being processed. `extract` gets the value; `extend` runs a context-aware function over it. Configuration threading without `Rc<Config>` everywhere. Stage 6 โ€” Limits/Colimits: Products combine validations (validate name AND age AND email โ€” all must succeed). Coproducts represent error alternatives (TooShort OR TooLong OR OutOfRange). The pipeline shape is a diagram; its categorical limit is the validated result type.

How It Works in Rust

// SUM + PRODUCT TYPES โ€” Data model
#[derive(Debug, Clone, PartialEq)]
enum ValidationError {        // SUM: one of these error kinds
 TooShort  { field: String, min: usize, got: usize },
 TooLong   { field: String, max: usize, got: usize },
 OutOfRange { field: String, min: i64, max: i64, got: i64 },
 // ...
}

#[derive(Debug, Clone)]
struct ValidatedUser {        // PRODUCT: ALL fields valid simultaneously
 name:  String,
 age:   u32,
 email: String,
 role:  Role,
}

// CURRY-HOWARD โ€” Each validator is a proof
fn validate_name(s: &str) -> Result<String, ValidationError> {
 if s.len() < 2 { return Err(ValidationError::TooShort { .. }); }  // proof of invalidity
 if s.len() > 50 { return Err(ValidationError::TooLong { .. }); }
 Ok(s.trim().to_string())  // proof of validity
}

// VALIDATED APPLICATIVE โ€” Accumulate all errors, not just first
#[derive(Debug, Clone)]
enum Validated<E, A> {
 Valid(A),
 Invalid(Vec<E>),  // collect ALL errors, not just one
}

impl<E: Clone, A> Validated<E, A> {
 // Applicative: combine two independent validations
 fn ap<B>(self, fb: Validated<E, B>) -> Validated<E, (A, B)>
 where E: Clone {
     match (self, fb) {
         (Validated::Valid(a),    Validated::Valid(b))    => Validated::Valid((a, b)),
         (Validated::Invalid(e1), Validated::Invalid(e2)) =>
             Validated::Invalid(e1.into_iter().chain(e2).collect()),  // merge errors
         (Validated::Invalid(e),  _) | (_, Validated::Invalid(e)) => Validated::Invalid(e),
     }
 }
}

// ENV COMONAD โ€” Context-aware processing (config threading)
struct Env<E, A> { env: E, value: A }

impl<E: Clone, A> Env<E, A> {
 fn extract(self) -> A { self.value }   // counit: get the value
 fn extend<B>(self, f: impl Fn(Env<E, A>) -> B) -> Env<E, B>  // cobind
 where E: Clone, A: Clone {
     let env = self.env.clone();
     let b = f(self);
     Env { env, value: b }
 }
}

// PROFUNCTOR โ€” Adapt pipeline stages
// dimap: transform both input and output of a processing step
let name_validator = Mapper::new(|raw: &str| validate_name(raw));
let user_name_lens = name_validator.dimap(
 |user: &RawInput| user.name.as_str(),  // extract name field
 |result| result,                        // pass through
);
The complete pipeline assembles these pieces: `RawInput` flows through profunctor-lifted validators, results are combined by the `Validated` applicative, configuration threads via the `Env` comonad, and the final `ValidatedUser` is the limit (product) of all successful validations.

What This Unlocks

Key Differences

ConceptOCamlRust
Sum typesPolymorphic variants or `type t = ...``enum ValidationError { ... }`
Validated applicativeModule functor over `Applicative``enum Validated<E,A>` with `ap` method
Env comonadModule `Env` with `extract`/`extend``struct Env<E,A>` with methods
Profunctor pipelineFirst-class module passing`Mapper<A,B>` with `dimap`/`first`
Error accumulation`(module Applicative)``Vec<E>` inside `Invalid` variant
/// Grand Synthesis: a capstone combining multiple category theory concepts.
///
/// This example builds a validated data pipeline that demonstrates:
///
///   1. Sum types & product types (data modeling)
///   2. Curry-Howard (type-safe proofs as validation functions)
///   3. Profunctor (adapting validation pipeline inputs/outputs)
///   4. Comonad (context-aware processing with Env comonad)
///   5. Adjunctions (State monad for pipeline state)
///   6. Limits/Colimits (products for combining validations, sums for errors)
///   7. Validated applicative (accumulating errors โ€” not short-circuiting)
///
/// The pipeline: raw input -> validated -> enriched -> formatted output

use std::collections::HashMap;
use std::rc::Rc;

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 1. SUM + PRODUCT TYPES โ€” The data model
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

#[derive(Debug, Clone, PartialEq)]
enum ValidationError {
    TooShort { field: String, min: usize, got: usize },
    TooLong  { field: String, max: usize, got: usize },
    OutOfRange { field: String, min: i64, max: i64, got: i64 },
    MissingChar { field: String, required: char },
    InvalidFormat { field: String, reason: String },
}

impl ValidationError {
    fn message(&self) -> String {
        match self {
            ValidationError::TooShort { field, min, got } =>
                format!("{}: too short (min {}, got {})", field, min, got),
            ValidationError::TooLong { field, max, got } =>
                format!("{}: too long (max {}, got {})", field, max, got),
            ValidationError::OutOfRange { field, min, max, got } =>
                format!("{}: {} out of range [{}, {}]", field, got, min, max),
            ValidationError::MissingChar { field, required } =>
                format!("{}: missing required character '{}'", field, required),
            ValidationError::InvalidFormat { field, reason } =>
                format!("{}: invalid format โ€” {}", field, reason),
        }
    }
}

#[derive(Debug, Clone)]
struct RawInput {
    name: String,
    age: String,
    email: String,
    role: String,
}

#[derive(Debug, Clone, PartialEq)]
struct ValidatedUser {
    name: String,
    age: u32,
    email: String,
    role: Role,
}

#[derive(Debug, Clone, PartialEq)]
enum Role {
    Admin,
    Editor,
    Viewer,
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 2. VALIDATED APPLICATIVE โ€” Colimit of errors (accumulating)
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/// Validated<A>: either a valid A or a non-empty list of errors.
/// Unlike Result, multiple errors accumulate (applicative style).
#[derive(Debug, Clone)]
enum Validated<A> {
    Ok(A),
    Err(Vec<ValidationError>),
}

impl<A: Clone> Validated<A> {
    fn map<B: Clone>(&self, f: impl Fn(A) -> B) -> Validated<B> {
        match self {
            Validated::Ok(a) => Validated::Ok(f(a.clone())),
            Validated::Err(es) => Validated::Err(es.clone()),
        }
    }

    fn and_then<B: Clone>(self, f: impl Fn(A) -> Validated<B>) -> Validated<B> {
        match self {
            Validated::Ok(a) => f(a),
            Validated::Err(es) => Validated::Err(es),
        }
    }

    fn is_ok(&self) -> bool {
        matches!(self, Validated::Ok(_))
    }
}

/// Applicative `apply`: accumulate errors from both sides.
/// This is the PRODUCT of two validated results (limit construction).
fn validated_apply<A: Clone, B: Clone>(
    vf: Validated<impl Fn(A) -> B + Clone>,
    va: Validated<A>,
) -> Validated<B> {
    match (vf, va) {
        (Validated::Ok(f), Validated::Ok(a)) => Validated::Ok(f(a)),
        (Validated::Err(e1), Validated::Ok(_)) => Validated::Err(e1),
        (Validated::Ok(_), Validated::Err(e2)) => Validated::Err(e2),
        (Validated::Err(mut e1), Validated::Err(e2)) => {
            e1.extend(e2); // key: accumulate ALL errors
            Validated::Err(e1)
        }
    }
}

fn ok<A>(a: A) -> Validated<A> { Validated::Ok(a) }
fn err<A>(e: ValidationError) -> Validated<A> { Validated::Err(vec![e]) }

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 3. PROFUNCTOR โ€” Adapting validation functions
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/// A validator is a Mapper<A, Validated<B>>.
/// We can use profunctor operations to adapt it.
struct Validator<A: 'static, B: Clone + 'static> {
    f: Rc<dyn Fn(A) -> Validated<B>>,
}

impl<A: Clone + 'static, B: Clone + 'static> Validator<A, B> {
    fn new(f: impl Fn(A) -> Validated<B> + 'static) -> Self {
        Validator { f: Rc::new(f) }
    }

    fn apply(&self, a: A) -> Validated<B> {
        (self.f)(a)
    }

    /// lmap: pre-process the input (contravariant)
    fn lmap<C: Clone + 'static>(self, pre: impl Fn(C) -> A + 'static) -> Validator<C, B> {
        Validator::new(move |c| (self.f)(pre(c)))
    }

    /// rmap: post-process the output (covariant)
    fn rmap<D: Clone + 'static>(self, post: impl Fn(B) -> D + 'static) -> Validator<A, D> {
        let f = self.f.clone();
        Validator::new(move |a| f(a).map(|b| post(b)))
    }
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 4. ENV COMONAD โ€” Context-aware processing
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/// Validation context: settings that affect validation rules.
#[derive(Debug, Clone)]
struct ValidationConfig {
    min_name_len: usize,
    max_name_len: usize,
    min_age: u32,
    max_age: u32,
    allowed_roles: Vec<String>,
    strict_email: bool,
}

impl Default for ValidationConfig {
    fn default() -> Self {
        ValidationConfig {
            min_name_len: 2,
            max_name_len: 50,
            min_age: 0,
            max_age: 130,
            allowed_roles: vec!["admin".to_string(), "editor".to_string(), "viewer".to_string()],
            strict_email: true,
        }
    }
}

/// Env comonad: value paired with environment
#[derive(Clone)]
struct Env<E: Clone, A: Clone> {
    env: E,
    value: A,
}

impl<E: Clone, A: Clone> Env<E, A> {
    fn new(env: E, value: A) -> Self { Env { env, value } }
    fn extract(&self) -> A { self.value.clone() }
    fn ask(&self) -> E { self.env.clone() }
    fn extend<B: Clone>(&self, f: impl Fn(&Env<E, A>) -> B) -> Env<E, B> {
        Env { env: self.env.clone(), value: f(self) }
    }
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 5. STATE MONAD โ€” Pipeline state (from adjunction)
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

#[derive(Debug, Clone, Default)]
struct PipelineState {
    processed: usize,
    errors: usize,
    warnings: Vec<String>,
}

#[derive(Clone)]
struct State<S: Clone, A: Clone> {
    run_fn: Rc<dyn Fn(S) -> (A, S)>,
}

impl<S: Clone + 'static, A: Clone + 'static> State<S, A> {
    fn new(f: impl Fn(S) -> (A, S) + 'static) -> Self {
        State { run_fn: Rc::new(f) }
    }
    fn run(&self, s: S) -> (A, S) { (self.run_fn)(s) }
    fn return_(a: A) -> Self { State::new(move |s| (a.clone(), s)) }
    fn bind<B: Clone + 'static>(self, f: impl Fn(A) -> State<S, B> + 'static) -> State<S, B> {
        State::new(move |s| {
            let (a, s2) = self.run(s);
            f(a).run(s2)
        })
    }
}

fn modify<S: Clone + 'static>(f: impl Fn(S) -> S + 'static) -> State<S, ()> {
    State::new(move |s| ((), f(s)))
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 6. VALIDATORS using Env comonad + Validated applicative
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

fn validate_name(cfg: &ValidationConfig, name: &str) -> Validated<String> {
    let len = name.trim().len();
    if len < cfg.min_name_len {
        err(ValidationError::TooShort { field: "name".into(), min: cfg.min_name_len, got: len })
    } else if len > cfg.max_name_len {
        err(ValidationError::TooLong { field: "name".into(), max: cfg.max_name_len, got: len })
    } else {
        ok(name.trim().to_string())
    }
}

fn validate_age_str(cfg: &ValidationConfig, age_str: &str) -> Validated<u32> {
    match age_str.trim().parse::<u32>() {
        Err(_) => err(ValidationError::InvalidFormat {
            field: "age".into(),
            reason: format!("'{}' is not a valid number", age_str)
        }),
        Ok(n) if n < cfg.min_age || n > cfg.max_age =>
            err(ValidationError::OutOfRange {
                field: "age".into(),
                min: cfg.min_age as i64, max: cfg.max_age as i64, got: n as i64
            }),
        Ok(n) => ok(n),
    }
}

fn validate_email(cfg: &ValidationConfig, email: &str) -> Validated<String> {
    let e = email.trim();
    if !e.contains('@') {
        return err(ValidationError::MissingChar { field: "email".into(), required: '@' });
    }
    if cfg.strict_email {
        let parts: Vec<&str> = e.splitn(2, '@').collect();
        if parts[0].is_empty() || !parts[1].contains('.') {
            return err(ValidationError::InvalidFormat {
                field: "email".into(),
                reason: "must be user@domain.tld".into()
            });
        }
    }
    ok(e.to_string())
}

fn validate_role(cfg: &ValidationConfig, role_str: &str) -> Validated<Role> {
    match role_str.trim().to_lowercase().as_str() {
        "admin" if cfg.allowed_roles.contains(&"admin".to_string()) => ok(Role::Admin),
        "editor" if cfg.allowed_roles.contains(&"editor".to_string()) => ok(Role::Editor),
        "viewer" if cfg.allowed_roles.contains(&"viewer".to_string()) => ok(Role::Viewer),
        other => err(ValidationError::InvalidFormat {
            field: "role".into(),
            reason: format!("'{}' not in allowed roles: {:?}", other, cfg.allowed_roles)
        }),
    }
}

/// Validate raw input using the Env comonad (config as environment).
fn validate_with_env(env: &Env<ValidationConfig, RawInput>) -> Validated<ValidatedUser> {
    let cfg = env.ask();
    let raw = env.extract();

    // Use Validated applicative: accumulate ALL errors
    let vname  = validate_name(&cfg, &raw.name);
    let vage   = validate_age_str(&cfg, &raw.age);
    let vemail = validate_email(&cfg, &raw.email);
    let vrole  = validate_role(&cfg, &raw.role);

    // Combine via applicative (product = limit):
    // ValidatedUser { name, age, email, role }
    let make_user = |name: String| move |age: u32| {
        let name2 = name.clone();
        move |email: String| {
            let name3 = name2.clone();
            let email2 = email.clone();
            move |role: Role| ValidatedUser { name: name3.clone(), age, email: email2.clone(), role }
        }
    };

    validated_apply(
        validated_apply(
            validated_apply(
                vname.map(make_user),
                vage,
            ),
            vemail,
        ),
        vrole,
    )
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 7. STATE-TRACKED PIPELINE
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

fn process_one(
    cfg: ValidationConfig,
    raw: RawInput,
) -> State<PipelineState, Result<ValidatedUser, Vec<String>>> {
    let env = Env::new(cfg, raw);
    let result = validate_with_env(&env);

    match result {
        Validated::Ok(user) => {
            let user2 = user.clone();
            modify(|mut s: PipelineState| { s.processed += 1; s })
                .bind(move |_| State::return_(Result::Ok(user2.clone())))
        }
        Validated::Err(errors) => {
            let msgs: Vec<String> = errors.iter().map(|e| e.message()).collect();
            let msgs2 = msgs.clone();
            modify(|mut s: PipelineState| { s.errors += 1; s })
                .bind(move |_| State::return_(Result::Err(msgs2.clone())))
        }
    }
}

fn main() {
    println!("=== Grand Synthesis: Category Theory in a Validated Pipeline ===\n");
    println!("Combining: Sum/Product Types, Validated Applicative, Profunctor,");
    println!("           Env Comonad, State Monad (from adjunction)\n");

    let cfg = ValidationConfig::default();

    // โ”€โ”€ Test cases โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let inputs = vec![
        ("Alice (valid)", RawInput {
            name: "Alice".into(), age: "30".into(),
            email: "alice@example.com".into(), role: "admin".into()
        }),
        ("Bob (valid)", RawInput {
            name: "Bob".into(), age: "25".into(),
            email: "bob@corp.io".into(), role: "viewer".into()
        }),
        ("X (short name)", RawInput {
            name: "X".into(), age: "25".into(),
            email: "x@test.com".into(), role: "editor".into()
        }),
        ("Multi-error case", RawInput {
            name: "A".into(), age: "999".into(),
            email: "not-an-email".into(), role: "superuser".into()
        }),
        ("Bad age format", RawInput {
            name: "Charlie".into(), age: "twenty".into(),
            email: "c@d.org".into(), role: "viewer".into()
        }),
    ];

    println!("{:-<60}", "");

    // Run pipeline using State monad + Env comonad
    let mut state = PipelineState::default();
    let mut results: Vec<(String, Result<ValidatedUser, Vec<String>>)> = Vec::new();

    for (label, raw) in &inputs {
        let computation = process_one(cfg.clone(), raw.clone());
        let (result, new_state) = computation.run(state);
        state = new_state;
        results.push((label.to_string(), result));
    }

    for (label, result) in &results {
        match result {
            Result::Ok(user) => {
                println!("โœ“ {}", label);
                println!("  User: name={}, age={}, email={}, role={:?}",
                    user.name, user.age, user.email, user.role);
            }
            Result::Err(errors) => {
                println!("โœ— {}", label);
                for e in errors {
                    println!("  Error: {}", e);
                }
            }
        }
        println!();
    }

    println!("{:-<60}", "");
    println!("Pipeline statistics:");
    println!("  Processed (valid):   {}", state.processed);
    println!("  Failed (with errors): {}", state.errors);
    println!("  Total inputs:        {}", inputs.len());

    // โ”€โ”€ Profunctor demonstration โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    println!();
    println!("โ”€โ”€ Profunctor: adapt validators โ”€โ”€\n");

    // Validator<String, String> for name
    let name_validator = Validator::new(|name: String| validate_name(&ValidationConfig::default(), &name));

    // lmap: accept &str instead of String (contravariant)
    let name_validator_str = name_validator.lmap(|s: &str| s.to_string());

    // rmap: uppercase the valid name (covariant)
    let name_upper = name_validator_str.rmap(|n: String| n.to_uppercase());

    println!("Profunctor dimap (name validator, &str -> uppercase):");
    match name_upper.apply("alice") {
        Validated::Ok(n) => println!("  \"alice\" -> Ok(\"{}\")", n),
        Validated::Err(_) => println!("  Error"),
    }
    match name_upper.apply("X") {
        Validated::Ok(n) => println!("  \"X\" -> Ok(\"{}\")", n),
        Validated::Err(es) => println!("  \"X\" -> Err({})", es[0].message()),
    }

    // โ”€โ”€ Summary of CT concepts used โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    println!();
    println!("Category theory concepts demonstrated:");
    println!("  Sum types         โ†’ ValidationError (coproduct), Role");
    println!("  Product types     โ†’ ValidatedUser (tuple/record), ValidationConfig");
    println!("  Validated         โ†’ applicative functor (accumulates errors via coproduct)");
    println!("  validated_apply   โ†’ product of validations (limit construction)");
    println!("  Profunctor        โ†’ Validator<A,B> with lmap/rmap for adapting validators");
    println!("  Env comonad       โ†’ ValidationConfig as read-only environment");
    println!("  State monad       โ†’ PipelineState tracking processed/error counts");
    println!("  Adjunction        โ†’ State = Gโˆ˜F for Product โŠฃ Exponential");
    println!("  Curry-Howard      โ†’ each validator IS a proof (fn A -> Validated B)");
}

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

    fn default_cfg() -> ValidationConfig { ValidationConfig::default() }

    #[test]
    fn test_valid_user() {
        let raw = RawInput {
            name: "Alice".into(), age: "30".into(),
            email: "alice@example.com".into(), role: "admin".into(),
        };
        let env = Env::new(default_cfg(), raw);
        let result = validate_with_env(&env);
        assert!(result.is_ok());
    }

    #[test]
    fn test_name_too_short() {
        match validate_name(&default_cfg(), "X") {
            Validated::Err(es) => assert!(matches!(&es[0], ValidationError::TooShort { .. })),
            _ => panic!("expected error"),
        }
    }

    #[test]
    fn test_multiple_errors_accumulate() {
        let raw = RawInput {
            name: "A".into(), age: "999".into(),
            email: "bademail".into(), role: "superuser".into(),
        };
        let env = Env::new(default_cfg(), raw);
        match validate_with_env(&env) {
            Validated::Err(es) => assert!(es.len() >= 3, "expected 3+ errors, got {}", es.len()),
            _ => panic!("expected errors"),
        }
    }

    #[test]
    fn test_state_pipeline() {
        let cfg = default_cfg();
        let raw_ok  = RawInput { name: "Bob".into(), age: "25".into(), email: "b@c.com".into(), role: "viewer".into() };
        let raw_err = RawInput { name: "Z".into(),   age: "999".into(), email: "bad".into(), role: "x".into() };

        let prog = process_one(cfg.clone(), raw_ok)
            .bind(|_| process_one(cfg.clone(), raw_err));

        let (_, final_state) = prog.run(PipelineState::default());
        assert_eq!(final_state.processed, 1);
        assert_eq!(final_state.errors, 1);
    }

    #[test]
    fn test_profunctor_validator() {
        let v = Validator::new(|n: String| validate_name(&ValidationConfig::default(), &n));
        let v_upper = v.rmap(|n: String| n.to_uppercase());
        match v_upper.apply("alice".to_string()) {
            Validated::Ok(s) => assert_eq!(s, "ALICE"),
            _ => panic!("expected Ok"),
        }
    }

    #[test]
    fn test_env_comonad_extend() {
        let env = Env::new(default_cfg(), "Alice".to_string());
        let extended = env.extend(|e| {
            let name = e.extract();
            let cfg = e.ask();
            name.len() >= cfg.min_name_len
        });
        assert!(extended.extract());
    }
}
(* Grand synthesis: a mini functional system combining:
   - Type-safe DSL (tagless final)
   - Monadic error handling
   - Functors and natural transformations
   - CPS and trampolining
   The goal: a validated data pipeline with logging *)

(* Validation applicative *)
type ('a, 'e) validated =
  | Valid   of 'a
  | Invalid of 'e list

let valid x    = Valid x
let invalid e  = Invalid [e]

let map_v f = function
  | Valid x     -> Valid (f x)
  | Invalid es  -> Invalid es

let apply_v f_v x_v = match f_v, x_v with
  | Valid f, Valid x             -> Valid (f x)
  | Invalid e, Valid _           -> Invalid e
  | Valid _, Invalid e           -> Invalid e
  | Invalid e1, Invalid e2       -> Invalid (e1 @ e2)

(* Validated pipeline for a user record *)
type user = { name: string; age: int; email: string }

let validate_name s =
  if String.length s >= 2 then valid s
  else invalid "name too short (min 2 chars)"

let validate_age n =
  if n >= 0 && n < 150 then valid n
  else invalid (Printf.sprintf "age %d out of range" n)

let validate_email s =
  if String.contains s '@' then valid s
  else invalid "email missing @"

let make_user name age email = { name; age; email }

let validate_user name age email =
  apply_v
    (apply_v (map_v make_user (validate_name name))
             (validate_age age))
    (validate_email email)

(* CPS logger *)
let with_log label f x k =
  let result = f x in
  k (Printf.sprintf "[%s] %s -> " label x) result

let () =
  (* Valid user *)
  (match validate_user "Alice" 30 "alice@example.com" with
   | Valid u    -> Printf.printf "Valid user: %s, %d, %s\n" u.name u.age u.email
   | Invalid es -> Printf.printf "Errors: %s\n" (String.concat "; " es));

  (* Multiple validation errors accumulate *)
  (match validate_user "A" 200 "bad-email" with
   | Valid _    -> assert false
   | Invalid es ->
     Printf.printf "Errors (%d):\n" (List.length es);
     List.iter (Printf.printf "  - %s\n") es);

  Printf.printf "\nGrand synthesis: FP patterns compose.\n"