Skip to main content

debtmap/core/
monadic.rs

1use anyhow::{Context as AnyhowContext, Result};
2use std::fmt::Display;
3
4/// Extension trait for monadic Result operations
5pub trait ResultExt<T> {
6    /// Chain async-like operations
7    fn and_then_async<F, U>(self, f: F) -> Result<U>
8    where
9        F: FnOnce(T) -> Result<U>;
10
11    /// Provide alternative on error
12    fn or_else_with<F>(self, f: F) -> Result<T>
13    where
14        F: FnOnce() -> Result<T>;
15
16    /// Add context to error
17    fn map_err_context<C>(self, context: C) -> Result<T>
18    where
19        C: Display + Send + Sync + 'static;
20
21    /// Map the Ok value
22    fn map_ok<F, U>(self, f: F) -> Result<U>
23    where
24        F: FnOnce(T) -> U;
25
26    /// Tap into the Ok value without consuming
27    fn tap<F>(self, f: F) -> Result<T>
28    where
29        F: FnOnce(&T);
30
31    /// Tap into error without consuming
32    fn tap_err<F>(self, f: F) -> Result<T>
33    where
34        F: FnOnce(&anyhow::Error);
35}
36
37impl<T> ResultExt<T> for Result<T> {
38    fn and_then_async<F, U>(self, f: F) -> Result<U>
39    where
40        F: FnOnce(T) -> Result<U>,
41    {
42        self.and_then(f)
43    }
44
45    fn or_else_with<F>(self, f: F) -> Result<T>
46    where
47        F: FnOnce() -> Result<T>,
48    {
49        self.or_else(|_| f())
50    }
51
52    fn map_err_context<C>(self, context: C) -> Result<T>
53    where
54        C: Display + Send + Sync + 'static,
55    {
56        self.context(context)
57    }
58
59    fn map_ok<F, U>(self, f: F) -> Result<U>
60    where
61        F: FnOnce(T) -> U,
62    {
63        self.map(f)
64    }
65
66    fn tap<F>(self, f: F) -> Result<T>
67    where
68        F: FnOnce(&T),
69    {
70        if let Ok(ref value) = self {
71            f(value);
72        }
73        self
74    }
75
76    fn tap_err<F>(self, f: F) -> Result<T>
77    where
78        F: FnOnce(&anyhow::Error),
79    {
80        if let Err(ref e) = self {
81            f(e);
82        }
83        self
84    }
85}
86
87/// Extension trait for Option monadic operations
88pub trait OptionExt<T> {
89    /// Convert None to an error
90    fn ok_or_error<E>(self, error: E) -> Result<T>
91    where
92        E: Into<anyhow::Error>;
93
94    /// Chain operations on Some
95    fn and_then_some<F, U>(self, f: F) -> Option<U>
96    where
97        F: FnOnce(T) -> Option<U>;
98
99    /// Provide alternative on None
100    fn or_else_some<F>(self, f: F) -> Option<T>
101    where
102        F: FnOnce() -> Option<T>;
103
104    /// Filter with predicate
105    fn filter_some<F>(self, predicate: F) -> Option<T>
106    where
107        F: FnOnce(&T) -> bool;
108}
109
110impl<T> OptionExt<T> for Option<T> {
111    fn ok_or_error<E>(self, error: E) -> Result<T>
112    where
113        E: Into<anyhow::Error>,
114    {
115        self.ok_or_else(|| error.into())
116    }
117
118    fn and_then_some<F, U>(self, f: F) -> Option<U>
119    where
120        F: FnOnce(T) -> Option<U>,
121    {
122        self.and_then(f)
123    }
124
125    fn or_else_some<F>(self, f: F) -> Option<T>
126    where
127        F: FnOnce() -> Option<T>,
128    {
129        self.or_else(f)
130    }
131
132    fn filter_some<F>(self, predicate: F) -> Option<T>
133    where
134        F: FnOnce(&T) -> bool,
135    {
136        self.filter(predicate)
137    }
138}
139
140/// Applicative functor for parallel computations
141pub struct Applicative<T> {
142    values: Vec<T>,
143}
144
145impl<T> Applicative<T> {
146    /// Create new applicative
147    pub fn new(values: Vec<T>) -> Self {
148        Self { values }
149    }
150
151    /// Apply function to all values
152    pub fn apply<F, U>(self, f: F) -> Applicative<U>
153    where
154        F: Fn(T) -> U,
155    {
156        Applicative::new(self.values.into_iter().map(f).collect())
157    }
158
159    /// Apply function that may fail
160    pub fn apply_result<F, U>(self, f: F) -> Result<Applicative<U>>
161    where
162        F: Fn(T) -> Result<U>,
163    {
164        let results: Result<Vec<U>> = self.values.into_iter().map(f).collect();
165        results.map(Applicative::new)
166    }
167
168    /// Extract values
169    pub fn unwrap(self) -> Vec<T> {
170        self.values
171    }
172}
173
174/// Kleisli composition for Result types
175pub fn compose_results<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> Result<C>
176where
177    F: Fn(A) -> Result<B>,
178    G: Fn(B) -> Result<C>,
179{
180    move |a| f(a).and_then(&g)
181}
182
183/// Lift a pure function into Result context
184pub fn lift_result<T, U, F>(f: F) -> impl Fn(T) -> Result<U>
185where
186    F: Fn(T) -> U,
187{
188    move |t| Ok(f(t))
189}
190
191/// Sequence a vector of Results into a Result of vector
192pub fn sequence_results<T>(results: Vec<Result<T>>) -> Result<Vec<T>> {
193    results.into_iter().collect()
194}
195
196/// Traverse with a function that returns Result
197pub fn traverse_results<T, U, F>(values: Vec<T>, f: F) -> Result<Vec<U>>
198where
199    F: Fn(T) -> Result<U>,
200{
201    values.into_iter().map(f).collect()
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use anyhow::anyhow;
208
209    #[test]
210    fn test_result_ext() {
211        let result: Result<i32> = Ok(42);
212
213        let chained = result
214            .map_ok(|x| x * 2)
215            .and_then_async(|x| Ok(x + 1))
216            .tap(|x| println!("Value: {x}"));
217
218        assert_eq!(chained.unwrap(), 85);
219    }
220
221    #[test]
222    fn test_option_ext() {
223        let value = Some(42);
224
225        let result = value
226            .and_then_some(|x| Some(x * 2))
227            .filter_some(|&x| x > 50)
228            .ok_or_error(anyhow!("Value too small"));
229
230        assert!(result.is_ok());
231        assert_eq!(result.unwrap(), 84);
232    }
233
234    #[test]
235    fn test_kleisli_composition() {
236        let add_one = |x: i32| -> Result<i32> { Ok(x + 1) };
237        let double = |x: i32| -> Result<i32> { Ok(x * 2) };
238
239        let composed = compose_results(add_one, double);
240        assert_eq!(composed(5).unwrap(), 12); // (5 + 1) * 2
241    }
242
243    #[test]
244    fn test_sequence() {
245        let results = vec![Ok(1), Ok(2), Ok(3)];
246        let sequenced = sequence_results(results);
247        assert_eq!(sequenced.unwrap(), vec![1, 2, 3]);
248
249        let with_error = vec![Ok(1), Err(anyhow!("error")), Ok(3)];
250        let sequenced_err = sequence_results(with_error);
251        assert!(sequenced_err.is_err());
252    }
253}