csw-core 0.1.0

Core categorical structures for the Categorical Semantics Workbench - define categories and derive type systems
Documentation
//! Builder API for constructing categorical specifications.

use crate::{
    Associativity, BaseObject, CategorySpec, DiagonalSpec, StructuralFeatures,
    TensorSpec, TerminalSpec, ValidationError,
};

/// Builder for constructing categorical specifications.
///
/// Provides a fluent API for defining categories with various structural features.
///
/// # Example
///
/// ```rust
/// use csw_core::CategoryBuilder;
///
/// // Simply-Typed Lambda Calculus from CCC
/// let stlc = CategoryBuilder::new("STLC")
///     .with_base("Int")
///     .with_base("Bool")
///     .with_terminal()
///     .with_products()
///     .with_exponentials()
///     .cartesian()
///     .build()
///     .unwrap();
///
/// // Linear Lambda Calculus from SMCC
/// let linear = CategoryBuilder::new("Linear")
///     .with_base("Int")
///     .with_tensor()
///     .with_linear_hom()
///     .with_symmetry()
///     .linear()
///     .build()
///     .unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct CategoryBuilder {
    name: String,
    base_objects: Vec<BaseObject>,
    structure: StructuralFeatures,
}

impl CategoryBuilder {
    /// Create a new category builder with the given name.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            base_objects: Vec::new(),
            structure: StructuralFeatures::default(),
        }
    }

    /// Add a base/primitive type to the category.
    pub fn with_base(mut self, name: impl Into<String>) -> Self {
        self.base_objects.push(BaseObject {
            name: name.into(),
            description: None,
        });
        self
    }

    /// Add a base type with description.
    pub fn with_base_described(mut self, name: impl Into<String>, description: impl Into<String>) -> Self {
        self.base_objects.push(BaseObject {
            name: name.into(),
            description: Some(description.into()),
        });
        self
    }

    // === Limits ===

    /// Add terminal object (unit type, 1).
    pub fn with_terminal(mut self) -> Self {
        self.structure.terminal = true;
        self
    }

    /// Add binary products (pair types, A × B).
    pub fn with_products(mut self) -> Self {
        self.structure.products = true;
        self
    }

    /// Add initial object (empty type, 0).
    pub fn with_initial(mut self) -> Self {
        self.structure.initial = true;
        self
    }

    /// Add binary coproducts (sum types, A + B).
    pub fn with_coproducts(mut self) -> Self {
        self.structure.coproducts = true;
        self
    }

    // === Closed Structure ===

    /// Add exponentials (function types, A → B).
    ///
    /// This automatically enables products, as exponentials require them.
    pub fn with_exponentials(mut self) -> Self {
        self.structure.exponentials = true;
        self.structure.products = true; // Exponentials require products
        self
    }

    // === Monoidal Structure ===

    /// Add monoidal tensor product (A ⊗ B).
    pub fn with_tensor(mut self) -> Self {
        self.structure.tensor = Some(TensorSpec::default());
        self
    }

    /// Add tensor product with custom symbols.
    pub fn with_tensor_custom(
        mut self,
        symbol: impl Into<String>,
        unit_symbol: impl Into<String>,
        associativity: Associativity,
    ) -> Self {
        self.structure.tensor = Some(TensorSpec {
            symbol: symbol.into(),
            unit_symbol: unit_symbol.into(),
            associativity,
        });
        self
    }

    /// Add linear function types (A ⊸ B).
    ///
    /// This automatically enables tensor if not already present.
    pub fn with_linear_hom(mut self) -> Self {
        self.structure.linear_hom = true;
        if self.structure.tensor.is_none() {
            self.structure.tensor = Some(TensorSpec::default());
        }
        self
    }

    // === Structural Morphisms ===

    /// Add diagonal morphism (contraction/copying) for all objects.
    pub fn with_diagonal(mut self) -> Self {
        self.structure.diagonal = DiagonalSpec::Universal;
        self
    }

    /// Add diagonal morphism only for specified types.
    pub fn with_diagonal_restricted(mut self, types: Vec<String>) -> Self {
        self.structure.diagonal = DiagonalSpec::Restricted(types);
        self
    }

    /// Add terminal morphism (weakening/discarding) for all objects.
    pub fn with_weakening(mut self) -> Self {
        self.structure.terminal_morphism = TerminalSpec::Universal;
        self
    }

    /// Add terminal morphism only for specified types.
    pub fn with_weakening_restricted(mut self, types: Vec<String>) -> Self {
        self.structure.terminal_morphism = TerminalSpec::Restricted(types);
        self
    }

    /// Add symmetry/braiding (exchange rule).
    pub fn with_symmetry(mut self) -> Self {
        self.structure.symmetry = true;
        self
    }

    // === Convenience Methods ===

    /// Make the category cartesian (full structural rules).
    ///
    /// Enables diagonal, terminal morphism, and symmetry for all objects.
    pub fn cartesian(mut self) -> Self {
        self.structure.diagonal = DiagonalSpec::Universal;
        self.structure.terminal_morphism = TerminalSpec::Universal;
        self.structure.symmetry = true;
        self
    }

    /// Make the category linear (no copying, no discarding).
    pub fn linear(mut self) -> Self {
        self.structure.diagonal = DiagonalSpec::None;
        self.structure.terminal_morphism = TerminalSpec::None;
        self
    }

    /// Make the category affine (can discard but not copy).
    ///
    /// This models Rust's ownership semantics.
    pub fn affine(mut self) -> Self {
        self.structure.diagonal = DiagonalSpec::None;
        self.structure.terminal_morphism = TerminalSpec::Universal;
        self
    }

    /// Make the category relevant (can copy but not discard).
    pub fn relevant(mut self) -> Self {
        self.structure.diagonal = DiagonalSpec::Universal;
        self.structure.terminal_morphism = TerminalSpec::None;
        self
    }

    /// Build the categorical specification, validating coherence conditions.
    pub fn build(self) -> Result<CategorySpec, ValidationError> {
        self.validate()?;

        Ok(CategorySpec {
            name: self.name,
            base_objects: self.base_objects,
            structure: self.structure,
        })
    }

    /// Validate the specification for coherence.
    fn validate(&self) -> Result<(), ValidationError> {
        // Name cannot be empty
        if self.name.is_empty() {
            return Err(ValidationError::EmptyName);
        }

        // Check for duplicate base types
        let mut seen = std::collections::HashSet::new();
        for obj in &self.base_objects {
            if !seen.insert(&obj.name) {
                return Err(ValidationError::DuplicateBaseType(obj.name.clone()));
            }
        }

        // Exponentials require products
        if self.structure.exponentials && !self.structure.products {
            return Err(ValidationError::ExponentialsRequireProducts);
        }

        // Linear hom requires tensor
        if self.structure.linear_hom && self.structure.tensor.is_none() {
            return Err(ValidationError::LinearHomRequiresTensor);
        }

        Ok(())
    }
}

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

    #[test]
    fn test_build_ccc() {
        let spec = CategoryBuilder::new("CCC")
            .with_terminal()
            .with_products()
            .with_exponentials()
            .cartesian()
            .build()
            .unwrap();

        assert_eq!(spec.name, "CCC");
        assert!(spec.structure.terminal);
        assert!(spec.structure.products);
        assert!(spec.structure.exponentials);
        assert!(spec.is_cartesian());
    }

    #[test]
    fn test_build_smcc() {
        let spec = CategoryBuilder::new("SMCC")
            .with_tensor()
            .with_linear_hom()
            .with_symmetry()
            .linear()
            .build()
            .unwrap();

        assert_eq!(spec.name, "SMCC");
        assert!(spec.structure.tensor.is_some());
        assert!(spec.structure.linear_hom);
        assert!(spec.structure.symmetry);
        assert!(spec.is_linear());
    }

    #[test]
    fn test_build_affine() {
        let spec = CategoryBuilder::new("Affine")
            .with_tensor()
            .with_linear_hom()
            .affine()
            .build()
            .unwrap();

        assert!(spec.is_affine());
    }

    #[test]
    fn test_empty_name_fails() {
        let result = CategoryBuilder::new("").build();
        assert!(matches!(result, Err(ValidationError::EmptyName)));
    }

    #[test]
    fn test_duplicate_base_type_fails() {
        let result = CategoryBuilder::new("Test")
            .with_base("Int")
            .with_base("Int")
            .build();
        assert!(matches!(result, Err(ValidationError::DuplicateBaseType(_))));
    }
}