Skip to main content

karpal_recursion/
either.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4/// A simple sum type for use in apomorphism and other schemes.
5///
6/// `Left` typically represents early termination (an already-computed `Fix`),
7/// while `Right` represents continuation (a seed to unfold further).
8pub enum Either<L, R> {
9    Left(L),
10    Right(R),
11}
12
13impl<L, R> Either<L, R> {
14    /// Eliminate an `Either` by providing handlers for both cases.
15    pub fn either<T>(self, f: impl FnOnce(L) -> T, g: impl FnOnce(R) -> T) -> T {
16        match self {
17            Either::Left(l) => f(l),
18            Either::Right(r) => g(r),
19        }
20    }
21
22    /// Map over the `Left` value, leaving `Right` unchanged.
23    pub fn map_left<L2>(self, f: impl FnOnce(L) -> L2) -> Either<L2, R> {
24        match self {
25            Either::Left(l) => Either::Left(f(l)),
26            Either::Right(r) => Either::Right(r),
27        }
28    }
29
30    /// Map over the `Right` value, leaving `Left` unchanged.
31    pub fn map_right<R2>(self, f: impl FnOnce(R) -> R2) -> Either<L, R2> {
32        match self {
33            Either::Left(l) => Either::Left(l),
34            Either::Right(r) => Either::Right(f(r)),
35        }
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn either_left() {
45        let e: Either<i32, &str> = Either::Left(42);
46        let result = e.either(|x| x.to_string(), |s| s.to_string());
47        assert_eq!(result, "42");
48    }
49
50    #[test]
51    fn either_right() {
52        let e: Either<i32, &str> = Either::Right("hello");
53        let result = e.either(|x| x.to_string(), |s| s.to_string());
54        assert_eq!(result, "hello");
55    }
56
57    #[test]
58    fn map_left() {
59        let e: Either<i32, &str> = Either::Left(10);
60        let mapped = e.map_left(|x| x * 2);
61        match mapped {
62            Either::Left(v) => assert_eq!(v, 20),
63            Either::Right(_) => panic!("expected Left"),
64        }
65    }
66
67    #[test]
68    fn map_right() {
69        let e: Either<i32, i32> = Either::Right(5);
70        let mapped = e.map_right(|x| x + 1);
71        match mapped {
72            Either::Right(v) => assert_eq!(v, 6),
73            Either::Left(_) => panic!("expected Right"),
74        }
75    }
76
77    #[test]
78    fn map_left_on_right_is_noop() {
79        let e: Either<i32, &str> = Either::Right("hi");
80        let mapped = e.map_left(|x| x * 100);
81        match mapped {
82            Either::Right(s) => assert_eq!(s, "hi"),
83            Either::Left(_) => panic!("expected Right"),
84        }
85    }
86
87    #[test]
88    fn map_right_on_left_is_noop() {
89        let e: Either<i32, i32> = Either::Left(7);
90        let mapped = e.map_right(|x| x * 100);
91        match mapped {
92            Either::Left(v) => assert_eq!(v, 7),
93            Either::Right(_) => panic!("expected Left"),
94        }
95    }
96}