Skip to main content

ftui_core/
generic_repr.rs

1//! Datatype-generic programming via Sum/Product/Unit encoding.
2//!
3//! Maps Rust structs and enums to a universal representation:
4//! - Structs → [`Product`] chains (e.g., `Product<A, Product<B, Unit>>`)
5//! - Enums → [`Sum`] chains (e.g., `Sum<A, Sum<B, Void>>`)
6//! - Empty → [`Unit`] or [`Void`]
7//!
8//! This enables generic algorithms like Diff, Patch, and StableHash
9//! that operate on the representation type and work for any type that
10//! implements [`GenericRepr`].
11//!
12//! # Example
13//!
14//! ```
15//! use ftui_core::generic_repr::*;
16//!
17//! #[derive(Clone, Debug, PartialEq)]
18//! struct Point { x: f64, y: f64 }
19//!
20//! impl GenericRepr for Point {
21//!     type Repr = Product<f64, Product<f64, Unit>>;
22//!
23//!     fn into_repr(self) -> Self::Repr {
24//!         Product(self.x, Product(self.y, Unit))
25//!     }
26//!
27//!     fn from_repr(repr: Self::Repr) -> Self {
28//!         Point { x: repr.0, y: repr.1.0 }
29//!     }
30//! }
31//!
32//! let p = Point { x: 1.0, y: 2.0 };
33//! let repr = p.clone().into_repr();
34//! let back = Point::from_repr(repr);
35//! assert_eq!(p, back);
36//! ```
37
38use std::fmt;
39
40/// Convert between a concrete type and its generic representation.
41///
42/// # Laws
43///
44/// 1. **Round-trip**: `T::from_repr(t.into_repr()) == t`
45/// 2. **Inverse**: `T::into_repr(T::from_repr(r)) == r`
46pub trait GenericRepr: Sized {
47    /// The generic representation type (built from Sum/Product/Unit/Void).
48    type Repr;
49
50    /// Convert this value to its generic representation.
51    fn into_repr(self) -> Self::Repr;
52
53    /// Reconstruct a value from its generic representation.
54    fn from_repr(repr: Self::Repr) -> Self;
55}
56
57// ── Product (struct fields) ─────────────────────────────────────────
58
59/// A product of two types (struct field pair).
60///
61/// Products chain to represent multiple fields:
62/// `Product<A, Product<B, Product<C, Unit>>>` represents a struct with fields A, B, C.
63#[derive(Clone, Copy, PartialEq, Eq, Hash)]
64pub struct Product<H, T>(pub H, pub T);
65
66impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for Product<H, T> {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "({:?} × {:?})", self.0, self.1)
69    }
70}
71
72/// The empty product (unit type for terminating product chains).
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
74pub struct Unit;
75
76// ── Sum (enum variants) ─────────────────────────────────────────────
77
78/// A sum of two types (enum with two variants).
79///
80/// Sums chain to represent multiple enum variants:
81/// `Sum<A, Sum<B, Sum<C, Void>>>` represents an enum with variants A, B, C.
82#[derive(Clone, Copy, PartialEq, Eq, Hash)]
83pub enum Sum<L, R> {
84    /// The left (earlier) variant.
85    Left(L),
86    /// The right (later) variant or remaining variants.
87    Right(R),
88}
89
90impl<L: fmt::Debug, R: fmt::Debug> fmt::Debug for Sum<L, R> {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Sum::Left(l) => write!(f, "Left({l:?})"),
94            Sum::Right(r) => write!(f, "Right({r:?})"),
95        }
96    }
97}
98
99/// The empty sum (uninhabited type for terminating sum chains).
100///
101/// Analogous to `!` (never type). No values of this type can exist.
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
103pub enum Void {}
104
105// ── Field metadata ──────────────────────────────────────────────────
106
107/// A named field wrapping a value.
108///
109/// Carries the field name as a const string for introspection by
110/// generic algorithms (e.g., Diff output can include field names).
111#[derive(Clone, Copy, PartialEq, Eq, Hash)]
112pub struct Field<T> {
113    /// The field name.
114    pub name: &'static str,
115    /// The field value.
116    pub value: T,
117}
118
119impl<T> Field<T> {
120    /// Create a named field.
121    pub fn new(name: &'static str, value: T) -> Self {
122        Self { name, value }
123    }
124
125    /// Map the value.
126    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Field<U> {
127        Field {
128            name: self.name,
129            value: f(self.value),
130        }
131    }
132}
133
134impl<T: fmt::Debug> fmt::Debug for Field<T> {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "{}={:?}", self.name, self.value)
137    }
138}
139
140/// A named variant wrapping a value.
141#[derive(Clone, Copy, PartialEq, Eq, Hash)]
142pub struct Variant<T> {
143    /// The variant name.
144    pub name: &'static str,
145    /// The variant payload.
146    pub value: T,
147}
148
149impl<T> Variant<T> {
150    pub fn new(name: &'static str, value: T) -> Self {
151        Self { name, value }
152    }
153}
154
155impl<T: fmt::Debug> fmt::Debug for Variant<T> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(f, "{}({:?})", self.name, self.value)
158    }
159}
160
161// ── GenericRepr impls for standard types ─────────────────────────────
162
163impl GenericRepr for () {
164    type Repr = Unit;
165    fn into_repr(self) -> Unit {
166        Unit
167    }
168    fn from_repr(_: Unit) -> Self {}
169}
170
171impl GenericRepr for bool {
172    type Repr = Sum<Unit, Unit>;
173    fn into_repr(self) -> Self::Repr {
174        if self {
175            Sum::Left(Unit)
176        } else {
177            Sum::Right(Unit)
178        }
179    }
180    fn from_repr(repr: Self::Repr) -> Self {
181        matches!(repr, Sum::Left(_))
182    }
183}
184
185impl<T> GenericRepr for Option<T> {
186    type Repr = Sum<T, Unit>;
187    fn into_repr(self) -> Self::Repr {
188        match self {
189            Some(v) => Sum::Left(v),
190            None => Sum::Right(Unit),
191        }
192    }
193    fn from_repr(repr: Self::Repr) -> Self {
194        match repr {
195            Sum::Left(v) => Some(v),
196            Sum::Right(_) => None,
197        }
198    }
199}
200
201impl<T, E> GenericRepr for Result<T, E> {
202    type Repr = Sum<T, E>;
203    fn into_repr(self) -> Self::Repr {
204        match self {
205            Ok(v) => Sum::Left(v),
206            Err(e) => Sum::Right(e),
207        }
208    }
209    fn from_repr(repr: Self::Repr) -> Self {
210        match repr {
211            Sum::Left(v) => Ok(v),
212            Sum::Right(e) => Err(e),
213        }
214    }
215}
216
217impl<A, B> GenericRepr for (A, B) {
218    type Repr = Product<A, Product<B, Unit>>;
219    fn into_repr(self) -> Self::Repr {
220        Product(self.0, Product(self.1, Unit))
221    }
222    fn from_repr(repr: Self::Repr) -> Self {
223        (repr.0, repr.1.0)
224    }
225}
226
227impl<A, B, C> GenericRepr for (A, B, C) {
228    type Repr = Product<A, Product<B, Product<C, Unit>>>;
229    fn into_repr(self) -> Self::Repr {
230        Product(self.0, Product(self.1, Product(self.2, Unit)))
231    }
232    fn from_repr(repr: Self::Repr) -> Self {
233        (repr.0, repr.1.0, repr.1.1.0)
234    }
235}
236
237// ── Utility traits for generic algorithms ───────────────────────────
238
239/// Count the number of fields in a product chain.
240pub trait ProductLen {
241    fn product_len() -> usize;
242}
243
244impl ProductLen for Unit {
245    fn product_len() -> usize {
246        0
247    }
248}
249
250impl<H, T: ProductLen> ProductLen for Product<H, T> {
251    fn product_len() -> usize {
252        1 + T::product_len()
253    }
254}
255
256/// Count the number of variants in a sum chain.
257pub trait SumLen {
258    fn sum_len() -> usize;
259}
260
261impl SumLen for Void {
262    fn sum_len() -> usize {
263        0
264    }
265}
266
267impl<L, R: SumLen> SumLen for Sum<L, R> {
268    fn sum_len() -> usize {
269        1 + R::sum_len()
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[derive(Clone, Debug, PartialEq)]
278    struct Point {
279        x: f64,
280        y: f64,
281    }
282
283    impl GenericRepr for Point {
284        type Repr = Product<Field<f64>, Product<Field<f64>, Unit>>;
285
286        fn into_repr(self) -> Self::Repr {
287            Product(
288                Field::new("x", self.x),
289                Product(Field::new("y", self.y), Unit),
290            )
291        }
292
293        fn from_repr(repr: Self::Repr) -> Self {
294            Point {
295                x: repr.0.value,
296                y: repr.1.0.value,
297            }
298        }
299    }
300
301    #[derive(Clone, Debug, PartialEq)]
302    enum Color {
303        Red,
304        Green,
305        Blue,
306        Custom(u8, u8, u8),
307    }
308
309    impl GenericRepr for Color {
310        type Repr = Sum<
311            Variant<Unit>,
312            Sum<
313                Variant<Unit>,
314                Sum<Variant<Unit>, Sum<Variant<Product<u8, Product<u8, Product<u8, Unit>>>>, Void>>,
315            >,
316        >;
317
318        fn into_repr(self) -> Self::Repr {
319            match self {
320                Color::Red => Sum::Left(Variant::new("Red", Unit)),
321                Color::Green => Sum::Right(Sum::Left(Variant::new("Green", Unit))),
322                Color::Blue => Sum::Right(Sum::Right(Sum::Left(Variant::new("Blue", Unit)))),
323                Color::Custom(r, g, b) => Sum::Right(Sum::Right(Sum::Right(Sum::Left(
324                    Variant::new("Custom", Product(r, Product(g, Product(b, Unit)))),
325                )))),
326            }
327        }
328
329        fn from_repr(repr: Self::Repr) -> Self {
330            match repr {
331                Sum::Left(_) => Color::Red,
332                Sum::Right(Sum::Left(_)) => Color::Green,
333                Sum::Right(Sum::Right(Sum::Left(_))) => Color::Blue,
334                Sum::Right(Sum::Right(Sum::Right(Sum::Left(v)))) => {
335                    Color::Custom(v.value.0, v.value.1.0, v.value.1.1.0)
336                }
337                Sum::Right(Sum::Right(Sum::Right(Sum::Right(v)))) => match v {},
338            }
339        }
340    }
341
342    // ── Round-trip law ──────────────────────────────────────────────
343
344    #[test]
345    fn point_roundtrip() {
346        let p = Point { x: 1.0, y: 2.0 };
347        let repr = p.clone().into_repr();
348        let back = Point::from_repr(repr);
349        assert_eq!(p, back);
350    }
351
352    #[test]
353    fn color_roundtrip_unit_variants() {
354        for c in [Color::Red, Color::Green, Color::Blue] {
355            let back = Color::from_repr(c.clone().into_repr());
356            assert_eq!(c, back);
357        }
358    }
359
360    #[test]
361    fn color_roundtrip_data_variant() {
362        let c = Color::Custom(255, 128, 0);
363        let back = Color::from_repr(c.clone().into_repr());
364        assert_eq!(c, back);
365    }
366
367    // ── Standard type impls ─────────────────────────────────────────
368
369    #[test]
370    fn unit_roundtrip() {
371        let repr = ().into_repr();
372        assert_eq!(repr, Unit);
373        assert_eq!(<()>::from_repr(repr), ());
374    }
375
376    #[test]
377    fn bool_roundtrip() {
378        assert!(bool::from_repr(true.into_repr()));
379        assert!(!bool::from_repr(false.into_repr()));
380    }
381
382    #[test]
383    fn option_roundtrip() {
384        let some = Some(42);
385        assert_eq!(Option::from_repr(some.into_repr()), Some(42));
386
387        let none: Option<i32> = None;
388        assert_eq!(Option::from_repr(none.into_repr()), None);
389    }
390
391    #[test]
392    fn result_roundtrip() {
393        let ok: Result<i32, String> = Ok(42);
394        assert_eq!(Result::from_repr(ok.into_repr()), Ok(42));
395
396        let err: Result<i32, String> = Err("fail".into());
397        assert_eq!(Result::from_repr(err.into_repr()), Err("fail".into()));
398    }
399
400    #[test]
401    fn tuple2_roundtrip() {
402        let t = (1u32, "hello");
403        let back = <(u32, &str)>::from_repr(t.into_repr());
404        assert_eq!(back, (1, "hello"));
405    }
406
407    #[test]
408    fn tuple3_roundtrip() {
409        let t = (1u32, 2.0f64, true);
410        let back = <(u32, f64, bool)>::from_repr(t.into_repr());
411        assert_eq!(back, (1, 2.0, true));
412    }
413
414    // ── Product/Sum length ──────────────────────────────────────────
415
416    #[test]
417    fn product_len_unit() {
418        assert_eq!(Unit::product_len(), 0);
419    }
420
421    #[test]
422    fn product_len_two() {
423        assert_eq!(<Product<i32, Product<i32, Unit>>>::product_len(), 2);
424    }
425
426    #[test]
427    fn product_len_three() {
428        assert_eq!(
429            <Product<i32, Product<i32, Product<i32, Unit>>>>::product_len(),
430            3
431        );
432    }
433
434    #[test]
435    fn sum_len_void() {
436        assert_eq!(Void::sum_len(), 0);
437    }
438
439    #[test]
440    fn sum_len_two() {
441        assert_eq!(<Sum<i32, Sum<i32, Void>>>::sum_len(), 2);
442    }
443
444    // ── Debug formatting ────────────────────────────────────────────
445
446    #[test]
447    fn product_debug() {
448        let p = Product(1, Product(2, Unit));
449        let debug = format!("{p:?}");
450        assert!(debug.contains("1"));
451        assert!(debug.contains("2"));
452    }
453
454    #[test]
455    fn sum_debug() {
456        let s: Sum<i32, i32> = Sum::Left(42);
457        assert_eq!(format!("{s:?}"), "Left(42)");
458    }
459
460    #[test]
461    fn field_debug() {
462        let f = Field::new("x", 1.0);
463        assert_eq!(format!("{f:?}"), "x=1.0");
464    }
465
466    #[test]
467    fn variant_debug() {
468        let v = Variant::new("Red", Unit);
469        let debug = format!("{v:?}");
470        assert!(debug.contains("Red"));
471    }
472
473    #[test]
474    fn field_map() {
475        let f = Field::new("count", 5);
476        let doubled = f.map(|v| v * 2);
477        assert_eq!(doubled.name, "count");
478        assert_eq!(doubled.value, 10);
479    }
480
481    // ── Named fields in repr ────────────────────────────────────────
482
483    #[test]
484    fn point_repr_has_field_names() {
485        let p = Point { x: 3.0, y: 4.0 };
486        let repr = p.into_repr();
487        assert_eq!(repr.0.name, "x");
488        assert_eq!(repr.0.value, 3.0);
489        assert_eq!(repr.1.0.name, "y");
490        assert_eq!(repr.1.0.value, 4.0);
491    }
492
493    #[test]
494    fn color_repr_has_variant_names() {
495        let c = Color::Red;
496        let repr = c.into_repr();
497        match repr {
498            Sum::Left(v) => assert_eq!(v.name, "Red"),
499            _ => panic!("expected Left"),
500        }
501    }
502}