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