catlog/dbl/
theory.rs

1//! Double theories.
2//!
3//! A double theory specifies a categorical structure, meaning a category (or
4//! categories) equipped with extra structure. The spirit of the formalism is that a
5//! double theory is "just" a [virtual double category](super::category),
6//! categorifying Lawvere's idea that a theory is "just" a category. Thus, a double
7//! theory is a [concept with an
8//! attitude](https://ncatlab.org/nlab/show/concept+with+an+attitude). To bring out
9//! these intuitions, the interface for double theories, [`DblTheory`], introduces
10//! new terminology compared to the references cited below.
11//!
12//! # Terminology
13//!
14//! A double theory comprises four kinds of things:
15//!
16//! 1. **Object type**, interpreted in models as a set of objects.
17//!
18//! 2. **Morphism type**, having a source and a target object type and interpreted
19//!    in models as a span of morphisms (or
20//!    [heteromorphisms](https://ncatlab.org/nlab/show/heteromorphism)) between sets
21//!    of objects.
22//!
23//! 3. **Object operation**, interpreted in models as a function between sets of
24//!    objects.
25//!
26//! 4. **Morphism operation**, having a source and target object operation and
27//!    interpreted in models as map between spans of morphisms.
28//!
29//! The dictionary between the type-theoretic and double-categorical terminology is
30//! summarized by the table:
31//!
32//! | Associated type                 | Double theory      | Double category           | Interpreted as |
33//! |---------------------------------|--------------------|---------------------------|----------------|
34//! | [`ObType`](DblTheory::ObType)   | Object type        | Object                    | Set            |
35//! | [`MorType`](DblTheory::MorType) | Morphism type      | Proarrow (loose morphism) | Span           |
36//! | [`ObOp`](DblTheory::ObOp)       | Object operation   | Arrow (tight morphism)    | Function       |
37//! | [`MorOp`](DblTheory::MorOp)     | Morphism operation | Cell                      | Map of spans   |
38//!
39//! Models of a double theory are *categorical* structures, rather than merely
40//! set-theoretical* ones, because each object type is assigned not just a set of
41//! objects but also a span of morphisms between those objects, constituting a
42//! category. The morphisms come from a distinguished "Hom" morphism type for each
43//! object type in the double theory. Similarly, each object operation is not just a
44//! function but a functor because it comes with an "Hom" operation between the Hom
45//! types. Moreover, morphism types can be composed to give new ones, as summarized
46//! by the table:
47//!
48//! | Method                                      | Double theory          | Double category        |
49//! |---------------------------------------------|------------------------|------------------------|
50//! | [`hom_type`](DblTheory::hom_type)           | Hom type               | Identity proarrow      |
51//! | [`hom_op`](DblTheory::hom_op)               | Hom operation          | Identity cell on arrow |
52//! | [`compose_types`](DblTheory::compose_types) | Compose morphism types | Compose proarrows      |
53//!
54//! Finally, operations on both objects and morphisms have identities and can be
55//! composed:
56//!
57//! | Method                                          | Double theory                       | Double category           |
58//! |-------------------------------------------------|-------------------------------------|---------------------------|
59//! | [`id_ob_op`](DblTheory::id_ob_op)               | Identity operation on object type   | Identity arrow            |
60//! | [`id_mor_op`](DblTheory::id_mor_op)             | Identity operation on morphism type | Identity cell on proarrow |
61//! | [`compose_ob_ops`](DblTheory::compose_ob_ops)   | Compose object operations           | Compose arrows            |
62//! | [`compose_mor_ops`](DblTheory::compose_mor_ops) | Compose morphism operations         | Compose cells             |
63//!
64//! # References
65//!
66//! - [Lambert & Patterson, 2024](crate::refs::CartDblTheories)
67//! - [Patterson, 2024](crate::refs::DblProducts),
68//!   Section 10: Finite-product double theories
69
70use std::fmt;
71
72use nonempty::NonEmpty;
73
74use super::graph::InvalidVDblGraph;
75use super::tree::DblTree;
76use crate::one::{InvalidPathEq, Path};
77use crate::zero::QualifiedName;
78
79pub use super::discrete::theory::*;
80pub use super::discrete_tabulator::theory::*;
81pub use super::modal::theory::*;
82
83/// The kind of a double theory, determining whether hom types are guaranteed.
84///
85/// Sealed trait machinery for [`DblTheoryKind`]: prevents external implementors.
86mod private {
87    pub trait Sealed {}
88    impl Sealed for super::Unital {}
89    impl Sealed for super::NonUnital {}
90}
91
92/// This trait uses a generic associated type ([`Wrap`](DblTheoryKind::Wrap)) to
93/// control the return type of [`DblTheory::hom_type`] and
94/// [`DblTheory::hom_op`]. For [`Unital`] theories, `Wrap<T>` is just `T`
95/// (hom types always exist). For [`NonUnital`] theories, `Wrap<T>` is
96/// `Option<T>` (hom types may not exist).
97///
98/// This trait is [sealed] and cannot be implemented outside this crate.
99///
100/// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
101pub trait DblTheoryKind: fmt::Debug + private::Sealed {
102    /// Wraps a type to reflect whether values are guaranteed to exist.
103    ///
104    /// For [`Unital`], this is the identity (`T`).
105    /// For [`NonUnital`], this is `Option<T>`.
106    type Wrap<T>;
107
108    /// Converts from an `Option` into a wrapped value.
109    ///
110    /// For [`Unital`], this unwraps with the given message.
111    /// For [`NonUnital`], this is the identity.
112    fn from_option<T>(opt: Option<T>, msg: &str) -> Self::Wrap<T>;
113}
114
115/// Unital double theories guarantee that every object type has a hom type.
116///
117/// Models of a categorical theory assign *categories* (not just sets) to each
118/// object type: the hom type provides the morphisms.
119#[derive(Clone, Debug, Default)]
120pub struct Unital;
121
122impl DblTheoryKind for Unital {
123    type Wrap<T> = T;
124
125    fn from_option<T>(opt: Option<T>, msg: &str) -> T {
126        opt.expect(msg)
127    }
128}
129
130/// Set-theoretic double theories make no guarantee that hom types exist.
131///
132/// The [`hom_type`](DblTheory::hom_type) method may return `None` for some or
133/// all object types.
134#[derive(Clone, Debug, Default)]
135pub struct NonUnital;
136
137impl DblTheoryKind for NonUnital {
138    type Wrap<T> = Option<T>;
139
140    fn from_option<T>(opt: Option<T>, _msg: &str) -> Option<T> {
141        opt
142    }
143}
144
145/// A double theory.
146///
147/// A double theory is a [virtual double category](super::category) viewed as
148/// specifying a categorical structure. The associated type [`Kind`](DblTheory::Kind)
149/// determines whether hom types are guaranteed to exist ([`Unital`]) or not
150/// ([`NonUnital`]).
151///
152/// See the [module-level docs](super::theory) for background on the terminology.
153pub trait DblTheory {
154    /// The kind of the theory: [`Unital`] or [`NonUnital`].
155    type Kind: DblTheoryKind;
156
157    /// Rust type of object types in the theory.
158    ///
159    /// Viewing the double theory as a virtual double category, this is the type of
160    /// objects.
161    type ObType: Eq + Clone;
162
163    /// Rust type of morphism types in the theory.
164    ///
165    /// Viewing the double theory as a virtual double category, this is the type of
166    /// proarrows.
167    type MorType: Eq + Clone;
168
169    /// Rust type of operations on objects in the double theory.
170    ///
171    /// Viewing the double theory as a virtual double category, this is the type of
172    /// arrows.
173    type ObOp: Eq + Clone;
174
175    /// Rust type of operations on morphisms in the double theory.
176    ///
177    /// Viewing the double theory as a virtual double category, this is the type of
178    /// cells.
179    type MorOp: Eq + Clone;
180
181    /// Does the object type belong to the theory?
182    fn has_ob_type(&self, x: &Self::ObType) -> bool;
183
184    /// Does the morphism type belong to the theory?
185    fn has_mor_type(&self, m: &Self::MorType) -> bool;
186
187    /// Does the object operation belong to the theory?
188    fn has_ob_op(&self, f: &Self::ObOp) -> bool;
189
190    /// Does the morphism operation belong to the theory?
191    fn has_mor_op(&self, α: &Self::MorOp) -> bool;
192
193    /// Source of a morphism type.
194    fn src_type(&self, m: &Self::MorType) -> Self::ObType;
195
196    /// Target of a morphism type.
197    fn tgt_type(&self, m: &Self::MorType) -> Self::ObType;
198
199    /// Domain of an operation on objects.
200    fn ob_op_dom(&self, f: &Self::ObOp) -> Self::ObType;
201
202    /// Codomain of an operation on objects.
203    fn ob_op_cod(&self, f: &Self::ObOp) -> Self::ObType;
204
205    /// Source operation of an operation on morphisms.
206    fn src_op(&self, α: &Self::MorOp) -> Self::ObOp;
207
208    /// Target operation of an operation on morphisms.
209    fn tgt_op(&self, α: &Self::MorOp) -> Self::ObOp;
210
211    /// Domain of an operation on morphisms, a path of morphism types.
212    fn mor_op_dom(&self, α: &Self::MorOp) -> Path<Self::ObType, Self::MorType>;
213
214    /// Codomain of an operation on morphisms, a single morphism type.
215    fn mor_op_cod(&self, α: &Self::MorOp) -> Self::MorType;
216
217    /// Composes a sequence of morphism types, if they have a composite.
218    fn compose_types(&self, path: Path<Self::ObType, Self::MorType>) -> Option<Self::MorType>;
219
220    /// Hom morphism type on an object type.
221    ///
222    /// Viewing the double theory as a virtual double category, this is the unit
223    /// proarrow on an object.
224    ///
225    /// For [`Unital`] theories, this returns `Self::MorType` directly.
226    /// For [`NonUnital`] theories, this returns `Option<Self::MorType>`.
227    fn hom_type(&self, x: Self::ObType) -> <Self::Kind as DblTheoryKind>::Wrap<Self::MorType>;
228
229    /// Compose a sequence of operations on objects.
230    fn compose_ob_ops(&self, path: Path<Self::ObType, Self::ObOp>) -> Self::ObOp;
231
232    /// Identity operation on an object type.
233    ///
234    /// View the double theory as a virtual double category, this is the identity
235    /// arrow on an object.
236    fn id_ob_op(&self, x: Self::ObType) -> Self::ObOp {
237        self.compose_ob_ops(Path::Id(x))
238    }
239
240    /// Hom morphism operation on an object operation.
241    ///
242    /// Viewing the double theory as a virtual double category, this is the unit
243    /// cell on an arrow.
244    ///
245    /// For [`Unital`] theories, this returns `Self::MorOp` directly.
246    /// For [`NonUnital`] theories, this returns `Option<Self::MorOp>`.
247    fn hom_op(&self, f: Self::ObOp) -> <Self::Kind as DblTheoryKind>::Wrap<Self::MorOp>;
248
249    /// Compose operations on morphisms.
250    fn compose_mor_ops(&self, tree: DblTree<Self::ObOp, Self::MorType, Self::MorOp>)
251    -> Self::MorOp;
252
253    /// Identity operation on a morphism type.
254    ///
255    /// Viewing the double theory as a virtual double category, this is the identity
256    /// cell on a proarrow.
257    fn id_mor_op(&self, m: Self::MorType) -> Self::MorOp {
258        self.compose_mor_ops(DblTree::empty(m))
259    }
260}
261
262/// Implements [`DblTheory`] for a type that implements [`VDCWithComposites`].
263///
264/// Two forms are supported:
265///
266/// - `impl_dbl_theory!(Type, Kind)`: for a concrete kind (`Unital` or `NonUnital`).
267/// - `impl_dbl_theory!(Type<Kind>)`: for a type generic over `Kind: DblTheoryKind`,
268///   producing a single generic impl.
269macro_rules! impl_dbl_theory {
270    ($ty:ty, $kind:ty) => {
271        impl $crate::dbl::theory::DblTheory for $ty {
272            type Kind = $kind;
273            $crate::dbl::theory::impl_dbl_theory!(@body);
274        }
275    };
276    ($ty:ident < $kind:ident >) => {
277        impl<$kind: $crate::dbl::theory::DblTheoryKind> $crate::dbl::theory::DblTheory
278            for $ty<$kind>
279        {
280            type Kind = $kind;
281            $crate::dbl::theory::impl_dbl_theory!(@body);
282        }
283    };
284    (@body) => {
285        type ObType = <Self as $crate::dbl::category::VDblCategory>::Ob;
286        type MorType = <Self as $crate::dbl::category::VDblCategory>::Pro;
287        type ObOp = <Self as $crate::dbl::category::VDblCategory>::Arr;
288        type MorOp = <Self as $crate::dbl::category::VDblCategory>::Cell;
289
290        fn has_ob_type(&self, x: &Self::ObType) -> bool {
291            $crate::dbl::category::VDblCategory::has_ob(self, x)
292        }
293        fn has_mor_type(&self, m: &Self::MorType) -> bool {
294            $crate::dbl::category::VDblCategory::has_proarrow(self, m)
295        }
296        fn has_ob_op(&self, f: &Self::ObOp) -> bool {
297            $crate::dbl::category::VDblCategory::has_arrow(self, f)
298        }
299        fn has_mor_op(&self, α: &Self::MorOp) -> bool {
300            $crate::dbl::category::VDblCategory::has_cell(self, α)
301        }
302
303        fn src_type(&self, m: &Self::MorType) -> Self::ObType {
304            $crate::dbl::category::VDblCategory::src(self, m)
305        }
306        fn tgt_type(&self, m: &Self::MorType) -> Self::ObType {
307            $crate::dbl::category::VDblCategory::tgt(self, m)
308        }
309        fn ob_op_dom(&self, f: &Self::ObOp) -> Self::ObType {
310            $crate::dbl::category::VDblCategory::dom(self, f)
311        }
312        fn ob_op_cod(&self, f: &Self::ObOp) -> Self::ObType {
313            $crate::dbl::category::VDblCategory::cod(self, f)
314        }
315
316        fn src_op(&self, α: &Self::MorOp) -> Self::ObOp {
317            $crate::dbl::category::VDblCategory::cell_src(self, α)
318        }
319        fn tgt_op(&self, α: &Self::MorOp) -> Self::ObOp {
320            $crate::dbl::category::VDblCategory::cell_tgt(self, α)
321        }
322        fn mor_op_dom(
323            &self, α: &Self::MorOp,
324        ) -> $crate::one::Path<Self::ObType, Self::MorType> {
325            $crate::dbl::category::VDblCategory::cell_dom(self, α)
326        }
327        fn mor_op_cod(&self, α: &Self::MorOp) -> Self::MorType {
328            $crate::dbl::category::VDblCategory::cell_cod(self, α)
329        }
330
331        fn compose_types(
332            &self,
333            path: $crate::one::Path<Self::ObType, Self::MorType>,
334        ) -> Option<Self::MorType> {
335            $crate::dbl::category::VDCWithComposites::composite(self, path)
336        }
337
338        fn hom_type(
339            &self,
340            x: Self::ObType,
341        ) -> <Self::Kind as $crate::dbl::theory::DblTheoryKind>::Wrap<Self::MorType> {
342            <Self::Kind as $crate::dbl::theory::DblTheoryKind>::from_option(
343                $crate::dbl::category::VDCWithComposites::unit(self, x),
344                "Unital double theory should have all hom types",
345            )
346        }
347        fn hom_op(
348            &self,
349            f: Self::ObOp,
350        ) -> <Self::Kind as $crate::dbl::theory::DblTheoryKind>::Wrap<Self::MorOp> {
351            <Self::Kind as $crate::dbl::theory::DblTheoryKind>::from_option(
352                $crate::dbl::category::VDCWithComposites::unit_arrow(self, f),
353                "Unital double theory should have all hom ops",
354            )
355        }
356
357        fn compose_ob_ops(
358            &self,
359            path: $crate::one::Path<Self::ObType, Self::ObOp>,
360        ) -> Self::ObOp {
361            $crate::dbl::category::VDblCategory::compose(self, path)
362        }
363        fn compose_mor_ops(
364            &self,
365            tree: $crate::dbl::tree::DblTree<Self::ObOp, Self::MorType, Self::MorOp>,
366        ) -> Self::MorOp {
367            $crate::dbl::category::VDblCategory::compose_cells(self, tree)
368        }
369    };
370}
371
372pub(crate) use impl_dbl_theory;
373
374/// A failure of a double theory to be well defined.
375#[derive(Debug, PartialEq, Eq)]
376pub enum InvalidDblTheory {
377    /// Morphism type with an invalid source type.
378    SrcType(QualifiedName),
379
380    /// Morphism type with an invalid target type.
381    TgtType(QualifiedName),
382
383    /// Object operation with an invalid domain.
384    ObOpDom(QualifiedName),
385
386    /// Object operation with an invalid codomain.
387    ObOpCod(QualifiedName),
388
389    /// Morphism operation with an invalid domain.
390    MorOpDom(QualifiedName),
391
392    /// Morphism operation with an invalid codomain.
393    MorOpCod(QualifiedName),
394
395    /// Morphism operation with an invalid source operation.
396    SrcOp(QualifiedName),
397
398    /// Morphism operation with an invalid target operation.
399    TgtOp(QualifiedName),
400
401    /// Morphism operation having a boundary with incompatible corners.
402    MorOpBoundary(QualifiedName),
403
404    /// Equation between morphism types with one or more errors.
405    MorTypeEq(usize, NonEmpty<InvalidPathEq>),
406
407    /// Equation between object operations with one or more errors.
408    ObOpEq(usize, NonEmpty<InvalidPathEq>),
409}
410
411impl From<InvalidVDblGraph<QualifiedName, QualifiedName, QualifiedName>> for InvalidDblTheory {
412    fn from(err: InvalidVDblGraph<QualifiedName, QualifiedName, QualifiedName>) -> Self {
413        match err {
414            InvalidVDblGraph::Dom(id) => InvalidDblTheory::ObOpDom(id),
415            InvalidVDblGraph::Cod(id) => InvalidDblTheory::ObOpCod(id),
416            InvalidVDblGraph::Src(id) => InvalidDblTheory::SrcType(id),
417            InvalidVDblGraph::Tgt(id) => InvalidDblTheory::TgtType(id),
418            InvalidVDblGraph::SquareDom(id) => InvalidDblTheory::MorOpDom(id),
419            InvalidVDblGraph::SquareCod(id) => InvalidDblTheory::MorOpCod(id),
420            InvalidVDblGraph::SquareSrc(id) => InvalidDblTheory::SrcOp(id),
421            InvalidVDblGraph::SquareTgt(id) => InvalidDblTheory::TgtOp(id),
422            InvalidVDblGraph::NotSquare(id) => InvalidDblTheory::MorOpBoundary(id),
423        }
424    }
425}