Skip to main content

karpal_recursion/
either.rs

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