Skip to main content

oxilean_std/sum/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{
6    BinderInfo, Declaration, Environment, Expr, InductiveEnv, InductiveType, IntroRule, Level, Name,
7};
8
9use super::functions::*;
10
11/// Statistics about a collection of `Coproduct<A, B>` values.
12#[derive(Clone, Debug, Default)]
13pub struct SumStats {
14    /// Number of left values.
15    pub left_count: usize,
16    /// Number of right values.
17    pub right_count: usize,
18}
19impl SumStats {
20    /// Collect statistics from a slice.
21    pub fn from_slice<A, B>(xs: &[Coproduct<A, B>]) -> Self {
22        let left_count = xs.iter().filter(|c| c.is_left()).count();
23        let right_count = xs.len() - left_count;
24        Self {
25            left_count,
26            right_count,
27        }
28    }
29    /// Total count.
30    pub fn total(&self) -> usize {
31        self.left_count + self.right_count
32    }
33    /// Fraction of left values (0.0 to 1.0).
34    pub fn left_fraction(&self) -> f64 {
35        if self.total() == 0 {
36            0.0
37        } else {
38            self.left_count as f64 / self.total() as f64
39        }
40    }
41    /// Whether all values are left.
42    pub fn all_left(&self) -> bool {
43        self.right_count == 0 && self.total() > 0
44    }
45    /// Whether all values are right.
46    pub fn all_right(&self) -> bool {
47        self.left_count == 0 && self.total() > 0
48    }
49}
50/// A chain of operations on a coproduct that short-circuits on the left.
51pub struct SumChain<A, B> {
52    inner: Coproduct<A, B>,
53}
54impl<A, B> SumChain<A, B> {
55    /// Wrap a coproduct.
56    pub fn new(c: Coproduct<A, B>) -> Self {
57        Self { inner: c }
58    }
59    /// Map the right side, short-circuiting on the left.
60    pub fn map<C>(self, f: impl FnOnce(B) -> C) -> SumChain<A, C> {
61        SumChain {
62            inner: self.inner.map_right(f),
63        }
64    }
65    /// FlatMap the right side, short-circuiting on the left.
66    pub fn flat_map<C>(self, f: impl FnOnce(B) -> Coproduct<A, C>) -> SumChain<A, C> {
67        SumChain {
68            inner: match self.inner {
69                Coproduct::Inl(a) => Coproduct::Inl(a),
70                Coproduct::Inr(b) => f(b),
71            },
72        }
73    }
74    /// Unwrap the chain.
75    pub fn into_inner(self) -> Coproduct<A, B> {
76        self.inner
77    }
78    /// Whether the chain is in the success (right) state.
79    pub fn is_ok(&self) -> bool {
80        self.inner.is_right()
81    }
82    /// Whether the chain is in the error (left) state.
83    pub fn is_err(&self) -> bool {
84        self.inner.is_left()
85    }
86}
87/// Witness of the bifunctor structure on Sum: maps `Sum A B → Sum C D`.
88#[allow(dead_code)]
89pub struct SumBifunctor<A, B, C, D> {
90    /// Phantom for source left type.
91    _a: std::marker::PhantomData<A>,
92    /// Phantom for source right type.
93    _b: std::marker::PhantomData<B>,
94    /// Phantom for target left type.
95    _c: std::marker::PhantomData<C>,
96    /// Phantom for target right type.
97    _d: std::marker::PhantomData<D>,
98    /// Name tag for this bifunctor instance.
99    pub name: String,
100}
101impl<A, B, C, D> SumBifunctor<A, B, C, D> {
102    /// Create a new SumBifunctor.
103    pub fn new(name: impl Into<String>) -> Self {
104        SumBifunctor {
105            _a: std::marker::PhantomData,
106            _b: std::marker::PhantomData,
107            _c: std::marker::PhantomData,
108            _d: std::marker::PhantomData,
109            name: name.into(),
110        }
111    }
112    /// Apply the bifunctor.
113    pub fn apply(
114        &self,
115        s: Coproduct<A, B>,
116        f: impl FnOnce(A) -> C,
117        g: impl FnOnce(B) -> D,
118    ) -> Coproduct<C, D> {
119        s.bimap(f, g)
120    }
121}
122/// A general coproduct (sum) of two types, equivalent to `Result<A, B>` but
123/// without the error/success semantics. Both sides are equally valid.
124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
125pub enum Coproduct<A, B> {
126    /// The left injection.
127    Inl(A),
128    /// The right injection.
129    Inr(B),
130}
131impl<A, B> Coproduct<A, B> {
132    /// Construct `Inl a`.
133    pub fn inl(a: A) -> Self {
134        Coproduct::Inl(a)
135    }
136    /// Construct `Inr b`.
137    pub fn inr(b: B) -> Self {
138        Coproduct::Inr(b)
139    }
140    /// Return true if `Inl`.
141    pub fn is_left(&self) -> bool {
142        matches!(self, Coproduct::Inl(_))
143    }
144    /// Return true if `Inr`.
145    pub fn is_right(&self) -> bool {
146        matches!(self, Coproduct::Inr(_))
147    }
148    /// Eliminate the coproduct by providing a function for each side.
149    pub fn elim<C>(self, f: impl FnOnce(A) -> C, g: impl FnOnce(B) -> C) -> C {
150        match self {
151            Coproduct::Inl(a) => f(a),
152            Coproduct::Inr(b) => g(b),
153        }
154    }
155    /// Swap the two sides.
156    pub fn swap(self) -> Coproduct<B, A> {
157        match self {
158            Coproduct::Inl(a) => Coproduct::Inr(a),
159            Coproduct::Inr(b) => Coproduct::Inl(b),
160        }
161    }
162    /// Bifunctor map: apply `f` to `Inl` and `g` to `Inr`.
163    pub fn bimap<C, D>(self, f: impl FnOnce(A) -> C, g: impl FnOnce(B) -> D) -> Coproduct<C, D> {
164        match self {
165            Coproduct::Inl(a) => Coproduct::Inl(f(a)),
166            Coproduct::Inr(b) => Coproduct::Inr(g(b)),
167        }
168    }
169    /// Map only the left side.
170    pub fn map_left<C>(self, f: impl FnOnce(A) -> C) -> Coproduct<C, B> {
171        match self {
172            Coproduct::Inl(a) => Coproduct::Inl(f(a)),
173            Coproduct::Inr(b) => Coproduct::Inr(b),
174        }
175    }
176    /// Map only the right side.
177    pub fn map_right<D>(self, g: impl FnOnce(B) -> D) -> Coproduct<A, D> {
178        match self {
179            Coproduct::Inl(a) => Coproduct::Inl(a),
180            Coproduct::Inr(b) => Coproduct::Inr(g(b)),
181        }
182    }
183    /// Extract the left value or return a default.
184    pub fn left_or(self, default: A) -> A {
185        match self {
186            Coproduct::Inl(a) => a,
187            Coproduct::Inr(_) => default,
188        }
189    }
190    /// Extract the right value or return a default.
191    pub fn right_or(self, default: B) -> B {
192        match self {
193            Coproduct::Inl(_) => default,
194            Coproduct::Inr(b) => b,
195        }
196    }
197    /// Try to extract the left value.
198    pub fn into_left(self) -> Option<A> {
199        match self {
200            Coproduct::Inl(a) => Some(a),
201            Coproduct::Inr(_) => None,
202        }
203    }
204    /// Try to extract the right value.
205    pub fn into_right(self) -> Option<B> {
206        match self {
207            Coproduct::Inl(_) => None,
208            Coproduct::Inr(b) => Some(b),
209        }
210    }
211    /// Convert to `Result<B, A>` (left = error, right = ok — standard `Result` convention).
212    pub fn into_result(self) -> Result<B, A> {
213        match self {
214            Coproduct::Inl(a) => Err(a),
215            Coproduct::Inr(b) => Ok(b),
216        }
217    }
218    /// Convert from `Result<B, A>`.
219    pub fn from_result(r: Result<B, A>) -> Self {
220        match r {
221            Ok(b) => Coproduct::Inr(b),
222            Err(a) => Coproduct::Inl(a),
223        }
224    }
225}
226impl<A: Clone, B: Clone> Coproduct<A, B> {
227    /// Coalesce: if both sides have the same type, return the inner value.
228    pub fn merge(self) -> A
229    where
230        A: Clone,
231        B: Into<A>,
232    {
233        match self {
234            Coproduct::Inl(a) => a,
235            Coproduct::Inr(b) => b.into(),
236        }
237    }
238}
239/// A disjoint union of four types.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum Sum4<A, B, C, D> {
242    /// First injection.
243    In1(A),
244    /// Second injection.
245    In2(B),
246    /// Third injection.
247    In3(C),
248    /// Fourth injection.
249    In4(D),
250}
251impl<A, B, C, D> Sum4<A, B, C, D> {
252    /// Eliminate by providing one function per variant.
253    #[allow(clippy::too_many_arguments)]
254    pub fn elim<E>(
255        self,
256        f1: impl FnOnce(A) -> E,
257        f2: impl FnOnce(B) -> E,
258        f3: impl FnOnce(C) -> E,
259        f4: impl FnOnce(D) -> E,
260    ) -> E {
261        match self {
262            Sum4::In1(a) => f1(a),
263            Sum4::In2(b) => f2(b),
264            Sum4::In3(c) => f3(c),
265            Sum4::In4(d) => f4(d),
266        }
267    }
268    /// Tag of the active variant (1-indexed).
269    pub fn tag(&self) -> u8 {
270        match self {
271            Sum4::In1(_) => 1,
272            Sum4::In2(_) => 2,
273            Sum4::In3(_) => 3,
274            Sum4::In4(_) => 4,
275        }
276    }
277    /// Whether this is the first variant.
278    pub fn is_first(&self) -> bool {
279        matches!(self, Sum4::In1(_))
280    }
281    /// Whether this is the second variant.
282    pub fn is_second(&self) -> bool {
283        matches!(self, Sum4::In2(_))
284    }
285    /// Whether this is the third variant.
286    pub fn is_third(&self) -> bool {
287        matches!(self, Sum4::In3(_))
288    }
289    /// Whether this is the fourth variant.
290    pub fn is_fourth(&self) -> bool {
291        matches!(self, Sum4::In4(_))
292    }
293}
294/// A mapping from tag labels to descriptions.
295#[derive(Clone, Debug, Default)]
296pub struct InjectionMap {
297    labels: Vec<(u8, String)>,
298}
299impl InjectionMap {
300    /// Create an empty map.
301    pub fn new() -> Self {
302        Self::default()
303    }
304    /// Register a label for a tag.
305    pub fn register(&mut self, tag: u8, label: impl Into<String>) {
306        self.labels.push((tag, label.into()));
307    }
308    /// Look up a label for a tag.
309    pub fn get(&self, tag: u8) -> Option<&str> {
310        self.labels
311            .iter()
312            .find(|(t, _)| *t == tag)
313            .map(|(_, l)| l.as_str())
314    }
315    /// Number of registered labels.
316    pub fn len(&self) -> usize {
317        self.labels.len()
318    }
319    /// Whether the map is empty.
320    pub fn is_empty(&self) -> bool {
321        self.labels.is_empty()
322    }
323}
324/// A tagged union with an explicit tag for inspection.
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct Tagged<T, A> {
327    /// The discriminant / tag.
328    pub tag: T,
329    /// The payload.
330    pub value: A,
331}
332impl<T, A> Tagged<T, A> {
333    /// Create a new `Tagged` value.
334    pub fn new(tag: T, value: A) -> Self {
335        Tagged { tag, value }
336    }
337    /// Map the value while preserving the tag.
338    pub fn map<B>(self, f: impl FnOnce(A) -> B) -> Tagged<T, B> {
339        Tagged {
340            tag: self.tag,
341            value: f(self.value),
342        }
343    }
344    /// Map the tag while preserving the value.
345    pub fn map_tag<U>(self, f: impl FnOnce(T) -> U) -> Tagged<U, A> {
346        Tagged {
347            tag: f(self.tag),
348            value: self.value,
349        }
350    }
351}
352/// A tagged-union type using string discriminants.
353#[allow(dead_code)]
354pub struct TaggedUnionSm {
355    /// The discriminant tag.
356    pub tag: String,
357    /// The serialized payload.
358    pub payload: String,
359}
360impl TaggedUnionSm {
361    /// Create a new tagged union value.
362    pub fn new(tag: impl Into<String>, payload: impl Into<String>) -> Self {
363        TaggedUnionSm {
364            tag: tag.into(),
365            payload: payload.into(),
366        }
367    }
368    /// Render as a display string.
369    pub fn render(&self) -> String {
370        format!("{}({})", self.tag, self.payload)
371    }
372    /// Check the tag.
373    pub fn has_tag(&self, t: &str) -> bool {
374        self.tag == t
375    }
376}
377/// A symmetric product (non-dependent pair) for use with Sum.
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub struct Pair<A, B> {
380    /// First component.
381    pub fst: A,
382    /// Second component.
383    pub snd: B,
384}
385impl<A, B> Pair<A, B> {
386    /// Construct a `Pair`.
387    pub fn new(fst: A, snd: B) -> Self {
388        Pair { fst, snd }
389    }
390    /// Swap the pair.
391    pub fn swap(self) -> Pair<B, A> {
392        Pair {
393            fst: self.snd,
394            snd: self.fst,
395        }
396    }
397    /// Map the first component.
398    pub fn map_fst<C>(self, f: impl FnOnce(A) -> C) -> Pair<C, B> {
399        Pair {
400            fst: f(self.fst),
401            snd: self.snd,
402        }
403    }
404    /// Map the second component.
405    pub fn map_snd<D>(self, g: impl FnOnce(B) -> D) -> Pair<A, D> {
406        Pair {
407            fst: self.fst,
408            snd: g(self.snd),
409        }
410    }
411    /// Convert to a tuple.
412    pub fn into_tuple(self) -> (A, B) {
413        (self.fst, self.snd)
414    }
415    /// Convert from a tuple.
416    pub fn from_tuple(t: (A, B)) -> Self {
417        Pair { fst: t.0, snd: t.1 }
418    }
419}
420/// Partition result for Either (Sum) type, holding separated lefts and rights.
421#[allow(dead_code)]
422pub struct EitherPartitionSm<L, R> {
423    /// All left values.
424    pub lefts: Vec<L>,
425    /// All right values.
426    pub rights: Vec<R>,
427}
428impl<L, R> EitherPartitionSm<L, R> {
429    /// Create from a vector of Coproducts.
430    pub fn from_vec(xs: Vec<Coproduct<L, R>>) -> Self {
431        let (ls, rs) = partition(xs);
432        EitherPartitionSm {
433            lefts: ls,
434            rights: rs,
435        }
436    }
437    /// Total number of elements.
438    pub fn total(&self) -> usize {
439        self.lefts.len() + self.rights.len()
440    }
441    /// True if there are no left values.
442    pub fn all_right(&self) -> bool {
443        self.lefts.is_empty()
444    }
445    /// True if there are no right values.
446    pub fn all_left(&self) -> bool {
447        self.rights.is_empty()
448    }
449    /// Left count.
450    pub fn left_count(&self) -> usize {
451        self.lefts.len()
452    }
453    /// Right count.
454    pub fn right_count(&self) -> usize {
455        self.rights.len()
456    }
457}
458/// A traversal of a Sum type: applies an effectful function over the right side.
459#[allow(dead_code)]
460pub struct SumTraversal<A, B> {
461    /// Phantom for left type.
462    _a: std::marker::PhantomData<A>,
463    /// Phantom for right type.
464    _b: std::marker::PhantomData<B>,
465    /// Description of the traversal.
466    pub description: String,
467}
468impl<A, B> SumTraversal<A, B> {
469    /// Create a new SumTraversal.
470    pub fn new(description: impl Into<String>) -> Self {
471        SumTraversal {
472            _a: std::marker::PhantomData,
473            _b: std::marker::PhantomData,
474            description: description.into(),
475        }
476    }
477    /// Traverse: apply `f` to the right side, preserving left.
478    pub fn traverse_option<C>(
479        &self,
480        s: Coproduct<A, B>,
481        f: impl FnOnce(B) -> Option<C>,
482    ) -> Option<Coproduct<A, C>> {
483        match s {
484            Coproduct::Inl(a) => Some(Coproduct::Inl(a)),
485            Coproduct::Inr(b) => f(b).map(Coproduct::Inr),
486        }
487    }
488}
489/// Witness of the universal property of Sum: given `f: A → Z` and `g: B → Z`,
490/// there is a unique mediating map `Sum A B → Z`.
491#[allow(dead_code)]
492pub struct SumUniversal<A, B, Z> {
493    /// The left branch.
494    pub left_branch: std::marker::PhantomData<fn(A) -> Z>,
495    /// The right branch.
496    pub right_branch: std::marker::PhantomData<fn(B) -> Z>,
497    /// Description of the universal property.
498    pub description: String,
499}
500impl<A, B, Z> SumUniversal<A, B, Z> {
501    /// Create a new SumUniversal witness.
502    pub fn new(description: impl Into<String>) -> Self {
503        SumUniversal {
504            left_branch: std::marker::PhantomData,
505            right_branch: std::marker::PhantomData,
506            description: description.into(),
507        }
508    }
509    /// Apply the universal mediator using concrete functions.
510    pub fn mediate(&self, s: Coproduct<A, B>, f: impl FnOnce(A) -> Z, g: impl FnOnce(B) -> Z) -> Z {
511        s.elim(f, g)
512    }
513}
514/// A coproduct of three types.
515#[derive(Debug, Clone, PartialEq, Eq)]
516pub enum Sum3<A, B, C> {
517    /// First injection.
518    In1(A),
519    /// Second injection.
520    In2(B),
521    /// Third injection.
522    In3(C),
523}
524impl<A, B, C> Sum3<A, B, C> {
525    /// Eliminate by providing one function per variant.
526    pub fn elim<D>(
527        self,
528        f1: impl FnOnce(A) -> D,
529        f2: impl FnOnce(B) -> D,
530        f3: impl FnOnce(C) -> D,
531    ) -> D {
532        match self {
533            Sum3::In1(a) => f1(a),
534            Sum3::In2(b) => f2(b),
535            Sum3::In3(c) => f3(c),
536        }
537    }
538    /// Return true if this is the first variant.
539    pub fn is_first(&self) -> bool {
540        matches!(self, Sum3::In1(_))
541    }
542    /// Return true if this is the second variant.
543    pub fn is_second(&self) -> bool {
544        matches!(self, Sum3::In2(_))
545    }
546    /// Return true if this is the third variant.
547    pub fn is_third(&self) -> bool {
548        matches!(self, Sum3::In3(_))
549    }
550}