1use core::marker::PhantomData;
2
3use karpal_core::hkt::HKT;
4
5use crate::classes::{ApplicativeSt, ChainSt, FunctorSt};
6use crate::trans::MonadTrans;
7
8pub struct ExceptTF<E, M>(PhantomData<(E, M)>);
14
15impl<E: 'static, M: HKT> HKT for ExceptTF<E, M> {
16 type Of<A> = M::Of<Result<A, E>>;
17}
18
19impl<E: 'static, M: FunctorSt> MonadTrans<M> for ExceptTF<E, M> {
20 fn lift<A: 'static>(ma: M::Of<A>) -> M::Of<Result<A, E>> {
21 M::fmap_st(ma, Ok)
22 }
23}
24
25pub fn except_t_pure<E: 'static, M: ApplicativeSt, A: 'static>(a: A) -> M::Of<Result<A, E>> {
27 M::pure_st(Ok(a))
28}
29
30pub fn except_t_fmap<E: 'static, M: FunctorSt, A: 'static, B: 'static>(
32 fa: M::Of<Result<A, E>>,
33 f: impl Fn(A) -> B + 'static,
34) -> M::Of<Result<B, E>> {
35 M::fmap_st(fa, move |r| r.map(&f))
36}
37
38pub fn except_t_chain<E: 'static, M: ChainSt + ApplicativeSt, A: 'static, B: 'static>(
42 fa: M::Of<Result<A, E>>,
43 f: impl Fn(A) -> M::Of<Result<B, E>> + 'static,
44) -> M::Of<Result<B, E>> {
45 M::chain_st(fa, move |r| match r {
46 Ok(a) => f(a),
47 Err(e) => M::pure_st(Err(e)),
48 })
49}
50
51pub fn except_t_throw<E: 'static, M: ApplicativeSt, A: 'static>(e: E) -> M::Of<Result<A, E>> {
53 M::pure_st(Err(e))
54}
55
56pub fn except_t_catch<E: 'static, M: ChainSt + ApplicativeSt, A: 'static>(
58 fa: M::Of<Result<A, E>>,
59 handler: impl Fn(E) -> M::Of<Result<A, E>> + 'static,
60) -> M::Of<Result<A, E>> {
61 M::chain_st(fa, move |r| match r {
62 Ok(a) => M::pure_st(Ok(a)),
63 Err(e) => handler(e),
64 })
65}
66
67pub fn except_t_run<E, M: HKT, A>(fa: M::Of<Result<A, E>>) -> M::Of<Result<A, E>> {
69 fa
70}
71
72impl<E: 'static, M: FunctorSt> FunctorSt for ExceptTF<E, M> {
75 fn fmap_st<A: 'static, B: 'static>(
76 fa: M::Of<Result<A, E>>,
77 f: impl Fn(A) -> B + 'static,
78 ) -> M::Of<Result<B, E>> {
79 except_t_fmap::<E, M, A, B>(fa, f)
80 }
81}
82
83impl<E: 'static, M: ApplicativeSt> ApplicativeSt for ExceptTF<E, M> {
84 fn pure_st<A: 'static>(a: A) -> M::Of<Result<A, E>> {
85 except_t_pure::<E, M, A>(a)
86 }
87}
88
89impl<E: 'static, M: ChainSt + ApplicativeSt> ChainSt for ExceptTF<E, M> {
90 fn chain_st<A: 'static, B: 'static>(
91 fa: M::Of<Result<A, E>>,
92 f: impl Fn(A) -> M::Of<Result<B, E>> + 'static,
93 ) -> M::Of<Result<B, E>> {
94 except_t_chain::<E, M, A, B>(fa, f)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use karpal_core::hkt::{IdentityF, OptionF};
102
103 #[test]
104 fn except_t_pure_identity() {
105 let result = except_t_pure::<&str, IdentityF, i32>(42);
106 assert_eq!(result, Ok(42));
107 }
108
109 #[test]
110 fn except_t_pure_option() {
111 let result = except_t_pure::<&str, OptionF, i32>(42);
112 assert_eq!(result, Some(Ok(42)));
113 }
114
115 #[test]
116 fn except_t_fmap_ok() {
117 let val = except_t_pure::<&str, OptionF, i32>(10);
118 let result = except_t_fmap::<&str, OptionF, _, _>(val, |x| x * 3);
119 assert_eq!(result, Some(Ok(30)));
120 }
121
122 #[test]
123 fn except_t_fmap_err() {
124 let val: Option<Result<i32, &str>> = Some(Err("bad"));
125 let result = except_t_fmap::<&str, OptionF, _, _>(val, |x: i32| x * 3);
126 assert_eq!(result, Some(Err("bad")));
127 }
128
129 #[test]
130 fn except_t_fmap_none() {
131 let val: Option<Result<i32, &str>> = None;
132 let result = except_t_fmap::<&str, OptionF, _, _>(val, |x: i32| x * 3);
133 assert_eq!(result, None);
134 }
135
136 #[test]
137 fn except_t_chain_ok() {
138 let val = except_t_pure::<&str, OptionF, i32>(5);
139 let result = except_t_chain::<&str, OptionF, _, _>(val, |x| Some(Ok(x + 10)));
140 assert_eq!(result, Some(Ok(15)));
141 }
142
143 #[test]
144 fn except_t_chain_err_short_circuits() {
145 let val: Option<Result<i32, &str>> = Some(Err("fail"));
146 let result = except_t_chain::<&str, OptionF, _, _>(val, |x| Some(Ok(x + 10)));
147 assert_eq!(result, Some(Err("fail")));
148 }
149
150 #[test]
151 fn except_t_chain_none() {
152 let val: Option<Result<i32, &str>> = None;
153 let result = except_t_chain::<&str, OptionF, _, _>(val, |x| Some(Ok(x + 10)));
154 assert_eq!(result, None);
155 }
156
157 #[test]
158 fn except_t_throw_test() {
159 let result = except_t_throw::<&str, OptionF, i32>("oops");
160 assert_eq!(result, Some(Err("oops")));
161 }
162
163 #[test]
164 fn except_t_catch_recovers() {
165 let val: Option<Result<i32, &str>> = Some(Err("bad"));
166 let result = except_t_catch::<&str, OptionF, i32>(val, |_| Some(Ok(42)));
167 assert_eq!(result, Some(Ok(42)));
168 }
169
170 #[test]
171 fn except_t_catch_ok_passes_through() {
172 let val = except_t_pure::<&str, OptionF, i32>(10);
173 let result = except_t_catch::<&str, OptionF, i32>(val, |_| Some(Ok(42)));
174 assert_eq!(result, Some(Ok(10)));
175 }
176
177 #[test]
178 fn except_t_lift_option() {
179 let lifted = ExceptTF::<&str, OptionF>::lift(Some(42));
180 assert_eq!(lifted, Some(Ok(42)));
181 }
182
183 #[test]
184 fn except_t_lift_none() {
185 let lifted = ExceptTF::<&str, OptionF>::lift(None::<i32>);
186 assert_eq!(lifted, None);
187 }
188
189 #[test]
192 fn except_t_functor_st_trait() {
193 let val = except_t_pure::<&str, OptionF, i32>(5);
194 let result = ExceptTF::<&str, OptionF>::fmap_st(val, |x| x + 1);
195 assert_eq!(result, Some(Ok(6)));
196 }
197
198 #[test]
199 fn except_t_applicative_st_trait() {
200 let result = ExceptTF::<&str, OptionF>::pure_st(99);
201 assert_eq!(result, Some(Ok(99)));
202 }
203
204 #[test]
205 fn except_t_chain_st_trait() {
206 let val = ExceptTF::<&str, OptionF>::pure_st(5);
207 let result = ExceptTF::<&str, OptionF>::chain_st(val, |x| Some(Ok(x + 10)));
208 assert_eq!(result, Some(Ok(15)));
209 }
210}
211
212#[cfg(test)]
213mod law_tests {
214 use super::*;
215 use karpal_core::hkt::OptionF;
216 use proptest::prelude::*;
217
218 proptest! {
219 #[test]
221 fn except_t_functor_identity(x in any::<Option<Result<i32, i32>>>()) {
222 let left = except_t_fmap::<i32, OptionF, _, _>(x.clone(), |a| a);
223 prop_assert_eq!(left, x);
224 }
225
226 #[test]
228 fn except_t_functor_composition(x in any::<Option<Result<i16, i16>>>()) {
229 let f = |a: i16| a.wrapping_add(1);
230 let g = |a: i16| a.wrapping_mul(2);
231 let left = except_t_fmap::<i16, OptionF, _, _>(x.clone(), move |a| g(f(a)));
232 let right = except_t_fmap::<i16, OptionF, _, _>(
233 except_t_fmap::<i16, OptionF, _, _>(x, f),
234 g,
235 );
236 prop_assert_eq!(left, right);
237 }
238
239 #[test]
241 fn except_t_monad_left_identity(a in -100i32..100) {
242 let f = |x: i32| -> Option<Result<i32, &str>> { Some(Ok(x + 1)) };
243 let left = except_t_chain::<&str, OptionF, _, _>(
244 except_t_pure::<&str, OptionF, _>(a),
245 f,
246 );
247 let right = f(a);
248 prop_assert_eq!(left, right);
249 }
250
251 #[test]
253 fn except_t_monad_right_identity(x in any::<Option<Result<i32, i32>>>()) {
254 let left = except_t_chain::<i32, OptionF, _, _>(
255 x.clone(),
256 |a| except_t_pure::<i32, OptionF, _>(a),
257 );
258 prop_assert_eq!(left, x);
259 }
260
261 #[test]
263 fn except_t_lift_pure(a in any::<i32>()) {
264 let lift_pure = ExceptTF::<&str, OptionF>::lift(Some(a));
265 let pure_a = except_t_pure::<&str, OptionF, _>(a);
266 prop_assert_eq!(lift_pure, pure_a);
267 }
268 }
269}