1#[fp_macros::document_module]
17mod inner {
18 use {
19 crate::{
20 classes::{
21 Deferrable,
22 LazyConfig,
23 Monoid,
24 Semigroup,
25 },
26 types::{
27 Lazy,
28 Thunk,
29 Trampoline,
30 TryLazy,
31 },
32 },
33 core::ops::ControlFlow,
34 fp_macros::*,
35 std::fmt,
36 };
37
38 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
91 pub struct TryTrampoline<A: 'static, E: 'static>(
93 Trampoline<Result<A, E>>,
95 );
96
97 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
98 #[document_parameters("The fallible trampoline computation.")]
99 impl<A: 'static, E: 'static> TryTrampoline<A, E> {
100 #[document_signature]
102 #[document_parameters("The success value.")]
104 #[document_returns("A `TryTrampoline` representing success.")]
106 #[document_examples]
108 #[inline]
116 pub fn ok(a: A) -> Self {
117 TryTrampoline(Trampoline::pure(Ok(a)))
118 }
119
120 #[document_signature]
125 #[document_parameters("The success value.")]
127 #[document_returns("A `TryTrampoline` representing success.")]
129 #[document_examples]
131 pub fn pure(a: A) -> Self {
139 Self::ok(a)
140 }
141
142 #[document_signature]
144 #[document_returns("The inner `Trampoline<Result<A, E>>`.")]
146 #[document_examples]
148 pub fn into_inner(self) -> Trampoline<Result<A, E>> {
157 self.0
158 }
159
160 #[document_signature]
162 #[document_parameters("The error value.")]
164 #[document_returns("A `TryTrampoline` representing failure.")]
166 #[document_examples]
168 #[inline]
176 pub fn err(e: E) -> Self {
177 TryTrampoline(Trampoline::pure(Err(e)))
178 }
179
180 #[document_signature]
182 #[document_parameters("The closure to execute.")]
184 #[document_returns("A `TryTrampoline` that executes `f` when run.")]
186 #[document_examples]
188 #[inline]
196 pub fn new(f: impl FnOnce() -> Result<A, E> + 'static) -> Self {
197 TryTrampoline(Trampoline::new(f))
198 }
199
200 #[document_signature]
204 #[document_parameters("A thunk that returns the next step.")]
206 #[document_returns("A `TryTrampoline` that executes `f` to get the next step.")]
208 #[document_examples]
210 #[inline]
240 pub fn defer(f: impl FnOnce() -> TryTrampoline<A, E> + 'static) -> Self {
241 TryTrampoline(Trampoline::defer(move || f().0))
242 }
243
244 #[document_signature]
246 #[document_type_parameters("The type of the new success value.")]
248 #[document_parameters("The function to apply to the success value.")]
250 #[document_returns("A new `TryTrampoline` with the transformed success value.")]
252 #[document_examples]
254 #[inline]
262 pub fn map<B: 'static>(
263 self,
264 func: impl FnOnce(A) -> B + 'static,
265 ) -> TryTrampoline<B, E> {
266 TryTrampoline(self.0.map(|result| result.map(func)))
267 }
268
269 #[document_signature]
271 #[document_type_parameters("The type of the new error value.")]
273 #[document_parameters("The function to apply to the error value.")]
275 #[document_returns("A new `TryTrampoline` with the transformed error value.")]
277 #[document_examples]
279 #[inline]
288 pub fn map_err<E2: 'static>(
289 self,
290 func: impl FnOnce(E) -> E2 + 'static,
291 ) -> TryTrampoline<A, E2> {
292 TryTrampoline(self.0.map(|result| result.map_err(func)))
293 }
294
295 #[document_signature]
297 #[document_type_parameters(
299 "The type of the new success value.",
300 "The type of the new error value."
301 )]
302 #[document_parameters(
304 "The function to apply to the success value.",
305 "The function to apply to the error value."
306 )]
307 #[document_returns("A new `TryTrampoline` with both sides transformed.")]
309 #[document_examples]
311 #[inline]
324 pub fn bimap<B: 'static, F: 'static>(
325 self,
326 f: impl FnOnce(A) -> B + 'static,
327 g: impl FnOnce(E) -> F + 'static,
328 ) -> TryTrampoline<B, F> {
329 TryTrampoline(self.0.map(|result| match result {
330 Ok(a) => Ok(f(a)),
331 Err(e) => Err(g(e)),
332 }))
333 }
334
335 #[document_signature]
337 #[document_type_parameters("The type of the new success value.")]
339 #[document_parameters("The function to apply to the success value.")]
341 #[document_returns("A new `TryTrampoline` that chains `f` after this task.")]
343 #[document_examples]
345 #[inline]
353 pub fn bind<B: 'static>(
354 self,
355 f: impl FnOnce(A) -> TryTrampoline<B, E> + 'static,
356 ) -> TryTrampoline<B, E> {
357 TryTrampoline(self.0.bind(|result| match result {
358 Ok(a) => f(a).0,
359 Err(e) => Trampoline::pure(Err(e)),
360 }))
361 }
362
363 #[document_signature]
365 #[document_parameters("The function to apply to the error value.")]
367 #[document_returns("A new `TryTrampoline` that attempts to recover from failure.")]
369 #[document_examples]
371 #[inline]
380 pub fn catch(
381 self,
382 f: impl FnOnce(E) -> TryTrampoline<A, E> + 'static,
383 ) -> Self {
384 TryTrampoline(self.0.bind(|result| match result {
385 Ok(a) => Trampoline::pure(Ok(a)),
386 Err(e) => f(e).0,
387 }))
388 }
389
390 #[document_signature]
397 #[document_type_parameters("The error type produced by the recovery computation.")]
399 #[document_parameters("The monadic recovery function applied to the error value.")]
401 #[document_returns(
403 "A new `TryTrampoline` that either passes through the success value or uses the result of the recovery computation."
404 )]
405 #[document_examples]
407 #[inline]
420 pub fn catch_with<E2: 'static>(
421 self,
422 f: impl FnOnce(E) -> TryTrampoline<A, E2> + 'static,
423 ) -> TryTrampoline<A, E2> {
424 TryTrampoline(self.0.bind(move |result| match result {
425 Ok(a) => Trampoline::pure(Ok(a)),
426 Err(e) => f(e).0,
427 }))
428 }
429
430 #[document_signature]
434 #[document_type_parameters(
436 "The type of the second computation's success value.",
437 "The type of the combined result."
438 )]
439 #[document_parameters("The second computation.", "The function to combine the results.")]
441 #[document_returns("A new `TryTrampoline` producing the combined result.")]
443 #[document_examples]
445 #[inline]
455 pub fn lift2<B: 'static, C: 'static>(
456 self,
457 other: TryTrampoline<B, E>,
458 f: impl FnOnce(A, B) -> C + 'static,
459 ) -> TryTrampoline<C, E> {
460 self.bind(move |a| other.map(move |b| f(a, b)))
461 }
462
463 #[document_signature]
467 #[document_type_parameters("The type of the second computation's success value.")]
469 #[document_parameters("The second computation.")]
471 #[document_returns(
473 "A new `TryTrampoline` that runs both computations and returns the result of the second."
474 )]
475 #[document_examples]
477 #[inline]
487 pub fn then<B: 'static>(
488 self,
489 other: TryTrampoline<B, E>,
490 ) -> TryTrampoline<B, E> {
491 self.bind(move |_| other)
492 }
493
494 #[document_signature]
508 #[document_type_parameters("The type of the state.")]
510 #[document_parameters(
512 "The function that performs one step of the recursion.",
513 "The initial state."
514 )]
515 #[document_returns("A `TryTrampoline` that performs the recursion.")]
517 #[document_examples]
518 pub fn tail_rec_m<S: 'static>(
545 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
546 initial: S,
547 ) -> Self {
548 fn go<S: 'static, A: 'static, E: 'static>(
549 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
550 s: S,
551 ) -> Trampoline<Result<A, E>> {
552 Trampoline::defer(move || {
553 let result = f(s);
554 result.0.bind(move |r| match r {
555 Ok(ControlFlow::Continue(next)) => go(f, next),
556 Ok(ControlFlow::Break(a)) => Trampoline::pure(Ok(a)),
557 Err(e) => Trampoline::pure(Err(e)),
558 })
559 })
560 }
561 TryTrampoline(go(f, initial))
562 }
563
564 #[document_signature]
568 #[document_type_parameters("The type of the state.")]
570 #[document_parameters(
572 "The function that performs one step of the recursion.",
573 "The initial state."
574 )]
575 #[document_returns("A `TryTrampoline` that performs the recursion.")]
577 #[document_examples]
578 pub fn arc_tail_rec_m<S: 'static>(
610 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + 'static,
611 initial: S,
612 ) -> Self {
613 use std::sync::Arc;
614 let f = Arc::new(f);
615 let wrapper = move |s: S| {
616 let f = Arc::clone(&f);
617 f(s)
618 };
619 Self::tail_rec_m(wrapper, initial)
620 }
621
622 #[document_signature]
624 #[document_returns("The result of the computation.")]
626 #[document_examples]
628 #[inline]
636 pub fn evaluate(self) -> Result<A, E> {
637 self.0.evaluate()
638 }
639
640 #[document_signature]
646 #[document_parameters(
648 "The second `TryTrampoline` whose result will be combined with this one."
649 )]
650 #[document_returns("A new `TryTrampoline` producing the combined result.")]
652 #[document_examples]
654 #[inline]
663 pub fn append(
664 self,
665 other: TryTrampoline<A, E>,
666 ) -> TryTrampoline<A, E>
667 where
668 A: Semigroup + 'static, {
669 self.lift2(other, Semigroup::append)
670 }
671
672 #[document_signature]
674 #[document_returns(
676 "A `TryTrampoline` producing the monoid identity element wrapped in `Ok`."
677 )]
678 #[document_examples]
680 #[inline]
688 pub fn empty() -> TryTrampoline<A, E>
689 where
690 A: Monoid + 'static, {
691 TryTrampoline::ok(Monoid::empty())
692 }
693
694 #[document_signature]
699 #[document_returns(
701 "A memoized `RcTryLazy` value that evaluates this trampoline on first access."
702 )]
703 #[document_examples]
705 #[inline]
714 pub fn into_rc_try_lazy(self) -> crate::types::RcTryLazy<'static, A, E> {
715 crate::types::RcTryLazy::from(self)
716 }
717
718 #[document_signature]
723 #[document_returns("A thread-safe `ArcTryLazy` containing the eagerly evaluated result.")]
725 #[document_examples]
727 #[inline]
736 pub fn into_arc_try_lazy(self) -> crate::types::ArcTryLazy<'static, A, E>
737 where
738 A: Send + Sync,
739 E: Send + Sync, {
740 crate::types::ArcTryLazy::from(self)
741 }
742
743 #[document_signature]
754 #[document_returns(
756 "`Ok(result)` if the computation is finished, `Err(thunk)` if it is suspended."
757 )]
758 #[document_examples]
760 pub fn resume(self) -> Result<Result<A, E>, Thunk<'static, TryTrampoline<A, E>>> {
779 match self.0.resume() {
780 Ok(result) => Ok(result),
781 Err(thunk) => Err(thunk.map(TryTrampoline)),
782 }
783 }
784 }
785
786 #[document_type_parameters("The type of the computed value.", "The type of the error value.")]
787 impl<A: 'static, E: 'static> TryTrampoline<A, E> {
788 #[document_signature]
795 #[document_parameters(
797 "The closure that might panic.",
798 "The function that converts a panic payload into the error type."
799 )]
800 #[document_returns(
802 "A new `TryTrampoline` instance where panics are converted to `Err(E)` via the handler."
803 )]
804 #[document_examples]
806 pub fn catch_unwind_with(
822 f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static,
823 handler: impl FnOnce(Box<dyn std::any::Any + Send>) -> E + 'static,
824 ) -> Self {
825 Self::new(move || std::panic::catch_unwind(f).map_err(handler))
826 }
827 }
828
829 #[document_type_parameters("The type of the computed value.")]
830 impl<A: 'static> TryTrampoline<A, String> {
831 #[document_signature]
840 #[document_parameters("The closure that might panic.")]
842 #[document_returns(
844 "A new `TryTrampoline` instance where panics are converted to `Err(String)`."
845 )]
846 #[document_examples]
848 pub fn catch_unwind(f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static) -> Self {
861 Self::catch_unwind_with(f, crate::utils::panic_payload_to_string)
862 }
863 }
864
865 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
866 impl<A, E> From<Trampoline<A>> for TryTrampoline<A, E>
867 where
868 A: 'static,
869 E: 'static,
870 {
871 #[document_signature]
872 #[document_parameters("The trampoline computation to convert.")]
873 #[document_returns("A new `TryTrampoline` instance that wraps the trampoline.")]
874 #[document_examples]
875 fn from(task: Trampoline<A>) -> Self {
883 TryTrampoline(task.map(Ok))
884 }
885 }
886
887 #[document_type_parameters(
888 "The type of the success value.",
889 "The type of the error value.",
890 "The memoization configuration."
891 )]
892 impl<A, E, Config> From<Lazy<'static, A, Config>> for TryTrampoline<A, E>
893 where
894 A: Clone + 'static,
895 E: 'static,
896 Config: LazyConfig,
897 {
898 #[document_signature]
904 #[document_parameters("The lazy value to convert.")]
905 #[document_returns("A new `TryTrampoline` instance that wraps the lazy value.")]
906 #[document_examples]
907 fn from(memo: Lazy<'static, A, Config>) -> Self {
915 TryTrampoline(Trampoline::new(move || Ok(memo.evaluate().clone())))
916 }
917 }
918
919 #[document_type_parameters(
920 "The type of the success value.",
921 "The type of the error value.",
922 "The memoization configuration."
923 )]
924 impl<A, E, Config> From<TryLazy<'static, A, E, Config>> for TryTrampoline<A, E>
925 where
926 A: Clone + 'static,
927 E: Clone + 'static,
928 Config: LazyConfig,
929 {
930 #[document_signature]
935 #[document_parameters("The fallible lazy value to convert.")]
936 #[document_returns("A new `TryTrampoline` instance that wraps the fallible lazy value.")]
937 #[document_examples]
938 fn from(memo: TryLazy<'static, A, E, Config>) -> Self {
946 TryTrampoline(Trampoline::new(move || {
947 let result = memo.evaluate();
948 match result {
949 Ok(a) => Ok(a.clone()),
950 Err(e) => Err(e.clone()),
951 }
952 }))
953 }
954 }
955
956 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
957 impl<A, E> From<crate::types::TryThunk<'static, A, E>> for TryTrampoline<A, E>
958 where
959 A: 'static,
960 E: 'static,
961 {
962 #[document_signature]
967 #[document_parameters("The fallible thunk to convert.")]
968 #[document_returns("A new `TryTrampoline` instance that evaluates the thunk.")]
969 #[document_examples]
970 fn from(thunk: crate::types::TryThunk<'static, A, E>) -> Self {
978 TryTrampoline::new(move || thunk.evaluate())
979 }
980 }
981
982 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
983 impl<A, E> From<Result<A, E>> for TryTrampoline<A, E>
984 where
985 A: 'static,
986 E: 'static,
987 {
988 #[document_signature]
989 #[document_parameters("The result to convert.")]
990 #[document_returns("A new `TryTrampoline` instance that produces the result.")]
991 #[document_examples]
992 fn from(result: Result<A, E>) -> Self {
1002 TryTrampoline(Trampoline::pure(result))
1003 }
1004 }
1005
1006 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1007 impl<A, E> Deferrable<'static> for TryTrampoline<A, E>
1008 where
1009 A: 'static,
1010 E: 'static,
1011 {
1012 #[document_signature]
1014 #[document_parameters("A thunk that produces the value.")]
1016 #[document_returns("The deferred value.")]
1018 #[document_examples]
1020 fn defer(f: impl FnOnce() -> Self + 'static) -> Self
1033 where
1034 Self: Sized, {
1035 TryTrampoline(Trampoline::defer(move || f().0))
1036 }
1037 }
1038
1039 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1040 impl<A, E> Semigroup for TryTrampoline<A, E>
1041 where
1042 A: Semigroup + 'static,
1043 E: 'static,
1044 {
1045 #[document_signature]
1050 #[document_parameters(
1052 "The first `TryTrampoline` computation.",
1053 "The second `TryTrampoline` computation."
1054 )]
1055 #[document_returns(
1057 "A `TryTrampoline` that evaluates both and combines the results, or propagates the first error."
1058 )]
1059 #[document_examples]
1061 fn append(
1074 a: Self,
1075 b: Self,
1076 ) -> Self {
1077 a.lift2(b, Semigroup::append)
1078 }
1079 }
1080
1081 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1082 impl<A, E> Monoid for TryTrampoline<A, E>
1083 where
1084 A: Monoid + 'static,
1085 E: 'static,
1086 {
1087 #[document_signature]
1089 #[document_returns("A `TryTrampoline` that succeeds with the monoidal identity element.")]
1091 #[document_examples]
1093 fn empty() -> Self {
1104 TryTrampoline::ok(A::empty())
1105 }
1106 }
1107
1108 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1109 #[document_parameters("The try-trampoline to format.")]
1110 impl<A: 'static, E: 'static> fmt::Debug for TryTrampoline<A, E> {
1111 #[document_signature]
1113 #[document_parameters("The formatter.")]
1114 #[document_returns("The formatting result.")]
1115 #[document_examples]
1116 fn fmt(
1123 &self,
1124 f: &mut fmt::Formatter<'_>,
1125 ) -> fmt::Result {
1126 f.write_str("TryTrampoline(<unevaluated>)")
1127 }
1128 }
1129}
1130pub use inner::*;
1131
1132#[cfg(test)]
1133#[expect(
1134 clippy::unwrap_used,
1135 clippy::panic,
1136 reason = "Tests use panicking operations for brevity and clarity"
1137)]
1138mod tests {
1139 use {
1140 super::*,
1141 crate::types::Trampoline,
1142 core::ops::ControlFlow,
1143 quickcheck_macros::quickcheck,
1144 };
1145
1146 #[test]
1150 fn test_try_task_ok() {
1151 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1152 assert_eq!(task.evaluate(), Ok(42));
1153 }
1154
1155 #[test]
1159 fn test_try_task_err() {
1160 let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
1161 assert_eq!(task.evaluate(), Err("error".to_string()));
1162 }
1163
1164 #[test]
1168 fn test_try_task_map() {
1169 let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
1170 assert_eq!(task.evaluate(), Ok(20));
1171 }
1172
1173 #[test]
1177 fn test_try_task_map_err() {
1178 let task: TryTrampoline<i32, String> =
1179 TryTrampoline::err("error".to_string()).map_err(|e| e.to_uppercase());
1180 assert_eq!(task.evaluate(), Err("ERROR".to_string()));
1181 }
1182
1183 #[test]
1187 fn test_try_task_bind() {
1188 let task: TryTrampoline<i32, String> =
1189 TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
1190 assert_eq!(task.evaluate(), Ok(20));
1191 }
1192
1193 #[test]
1197 fn test_try_task_or_else() {
1198 let task: TryTrampoline<i32, String> =
1199 TryTrampoline::err("error".to_string()).catch(|_| TryTrampoline::ok(42));
1200 assert_eq!(task.evaluate(), Ok(42));
1201 }
1202
1203 #[test]
1207 fn test_catch_with_recovers() {
1208 let task: TryTrampoline<i32, i32> = TryTrampoline::<i32, String>::err("error".to_string())
1209 .catch_with(|_| TryTrampoline::err(42));
1210 assert_eq!(task.evaluate(), Err(42));
1211 }
1212
1213 #[test]
1217 fn test_catch_with_success_passes_through() {
1218 let task: TryTrampoline<i32, i32> =
1219 TryTrampoline::<i32, String>::ok(1).catch_with(|_| TryTrampoline::err(42));
1220 assert_eq!(task.evaluate(), Ok(1));
1221 }
1222
1223 #[test]
1227 fn test_try_task_new() {
1228 let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
1229 assert_eq!(task.evaluate(), Ok(42));
1230 }
1231
1232 #[test]
1236 fn test_try_trampoline_pure() {
1237 let task: TryTrampoline<i32, String> = TryTrampoline::pure(42);
1238 assert_eq!(task.evaluate(), Ok(42));
1239 }
1240
1241 #[test]
1245 fn test_try_trampoline_into_inner() {
1246 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1247 let inner: Trampoline<Result<i32, String>> = task.into_inner();
1248 assert_eq!(inner.evaluate(), Ok(42));
1249 }
1250
1251 #[test]
1253 fn test_try_task_from_task() {
1254 let task = Trampoline::pure(42);
1255 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(task);
1256 assert_eq!(try_task.evaluate(), Ok(42));
1257 }
1258
1259 #[test]
1261 fn test_try_task_from_memo() {
1262 use crate::types::ArcLazy;
1263 let memo = ArcLazy::new(|| 42);
1264 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1265 assert_eq!(try_task.evaluate(), Ok(42));
1266 }
1267
1268 #[test]
1270 fn test_try_task_from_try_memo() {
1271 use crate::types::ArcTryLazy;
1272 let memo = ArcTryLazy::new(|| Ok(42));
1273 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1274 assert_eq!(try_task.evaluate(), Ok(42));
1275 }
1276
1277 #[test]
1281 fn test_try_task_lift2_success() {
1282 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1283 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1284 let t3 = t1.lift2(t2, |a, b| a + b);
1285 assert_eq!(t3.evaluate(), Ok(30));
1286 }
1287
1288 #[test]
1292 fn test_try_task_lift2_first_error() {
1293 let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1294 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1295 let t3 = t1.lift2(t2, |a, b| a + b);
1296 assert_eq!(t3.evaluate(), Err("first".to_string()));
1297 }
1298
1299 #[test]
1303 fn test_try_task_lift2_second_error() {
1304 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1305 let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1306 let t3 = t1.lift2(t2, |a, b| a + b);
1307 assert_eq!(t3.evaluate(), Err("second".to_string()));
1308 }
1309
1310 #[test]
1314 fn test_try_task_then_success() {
1315 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1316 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1317 let t3 = t1.then(t2);
1318 assert_eq!(t3.evaluate(), Ok(20));
1319 }
1320
1321 #[test]
1325 fn test_try_task_then_first_error() {
1326 let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1327 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1328 let t3 = t1.then(t2);
1329 assert_eq!(t3.evaluate(), Err("first".to_string()));
1330 }
1331
1332 #[test]
1336 fn test_try_task_then_second_error() {
1337 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1338 let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1339 let t3 = t1.then(t2);
1340 assert_eq!(t3.evaluate(), Err("second".to_string()));
1341 }
1342
1343 #[test]
1347 fn test_try_task_tail_rec_m() {
1348 fn factorial(n: i32) -> TryTrampoline<i32, String> {
1349 TryTrampoline::tail_rec_m(
1350 |(n, acc)| {
1351 if n < 0 {
1352 TryTrampoline::err("Negative input".to_string())
1353 } else if n <= 1 {
1354 TryTrampoline::ok(ControlFlow::Break(acc))
1355 } else {
1356 TryTrampoline::ok(ControlFlow::Continue((n - 1, n * acc)))
1357 }
1358 },
1359 (n, 1),
1360 )
1361 }
1362
1363 assert_eq!(factorial(5).evaluate(), Ok(120));
1364 assert_eq!(factorial(0).evaluate(), Ok(1));
1365 assert_eq!(factorial(1).evaluate(), Ok(1));
1366 }
1367
1368 #[test]
1372 fn test_try_task_tail_rec_m_error() {
1373 let task: TryTrampoline<i32, String> = TryTrampoline::tail_rec_m(
1374 |n: i32| {
1375 if n >= 5 {
1376 TryTrampoline::err(format!("too large: {}", n))
1377 } else {
1378 TryTrampoline::ok(ControlFlow::Continue(n + 1))
1379 }
1380 },
1381 0,
1382 );
1383
1384 assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1385 }
1386
1387 #[test]
1391 fn test_try_task_tail_rec_m_stack_safety() {
1392 let n = 100_000u64;
1393 let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1394 |(remaining, acc)| {
1395 if remaining == 0 {
1396 TryTrampoline::ok(ControlFlow::Break(acc))
1397 } else {
1398 TryTrampoline::ok(ControlFlow::Continue((remaining - 1, acc + remaining)))
1399 }
1400 },
1401 (n, 0u64),
1402 );
1403
1404 assert_eq!(task.evaluate(), Ok(n * (n + 1) / 2));
1405 }
1406
1407 #[test]
1411 fn test_try_task_tail_rec_m_stack_safety_error() {
1412 let n = 100_000u64;
1413 let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1414 |remaining| {
1415 if remaining == 0 {
1416 TryTrampoline::err("done iterating".to_string())
1417 } else {
1418 TryTrampoline::ok(ControlFlow::Continue(remaining - 1))
1419 }
1420 },
1421 n,
1422 );
1423
1424 assert_eq!(task.evaluate(), Err("done iterating".to_string()));
1425 }
1426
1427 #[test]
1431 fn test_try_task_arc_tail_rec_m() {
1432 use std::sync::{
1433 Arc,
1434 atomic::{
1435 AtomicUsize,
1436 Ordering,
1437 },
1438 };
1439
1440 let counter = Arc::new(AtomicUsize::new(0));
1441 let counter_clone = Arc::clone(&counter);
1442
1443 let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1444 move |n| {
1445 counter_clone.fetch_add(1, Ordering::SeqCst);
1446 if n == 0 {
1447 TryTrampoline::ok(ControlFlow::Break(0))
1448 } else {
1449 TryTrampoline::ok(ControlFlow::Continue(n - 1))
1450 }
1451 },
1452 10,
1453 );
1454
1455 assert_eq!(task.evaluate(), Ok(0));
1456 assert_eq!(counter.load(Ordering::SeqCst), 11);
1457 }
1458
1459 #[test]
1463 fn test_try_task_arc_tail_rec_m_error() {
1464 use std::sync::{
1465 Arc,
1466 atomic::{
1467 AtomicUsize,
1468 Ordering,
1469 },
1470 };
1471
1472 let counter = Arc::new(AtomicUsize::new(0));
1473 let counter_clone = Arc::clone(&counter);
1474
1475 let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1476 move |n| {
1477 counter_clone.fetch_add(1, Ordering::SeqCst);
1478 if n >= 5 {
1479 TryTrampoline::err(format!("too large: {}", n))
1480 } else {
1481 TryTrampoline::ok(ControlFlow::Continue(n + 1))
1482 }
1483 },
1484 0,
1485 );
1486
1487 assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1488 assert_eq!(counter.load(Ordering::SeqCst), 6);
1490 }
1491
1492 #[test]
1494 fn test_try_task_from_try_thunk_ok() {
1495 use crate::types::TryThunk;
1496 let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1497 let task = TryTrampoline::from(thunk);
1498 assert_eq!(task.evaluate(), Ok(42));
1499 }
1500
1501 #[test]
1503 fn test_try_task_from_try_thunk_err() {
1504 use crate::types::TryThunk;
1505 let thunk = TryThunk::new(|| Err::<i32, String>("error".to_string()));
1506 let task = TryTrampoline::from(thunk);
1507 assert_eq!(task.evaluate(), Err("error".to_string()));
1508 }
1509
1510 #[test]
1514 fn test_try_thunk_try_trampoline_round_trip() {
1515 use crate::types::TryThunk;
1516
1517 let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1519 let tramp = TryTrampoline::from(thunk);
1520 let thunk_back: TryThunk<i32, String> = TryThunk::from(tramp);
1521 assert_eq!(thunk_back.evaluate(), Ok(42));
1522
1523 let tramp = TryTrampoline::err("fail".to_string());
1525 let thunk: TryThunk<i32, String> = TryThunk::from(tramp);
1526 let tramp_back = TryTrampoline::from(thunk);
1527 assert_eq!(tramp_back.evaluate(), Err("fail".to_string()));
1528 }
1529
1530 #[test]
1532 fn test_try_task_from_result_ok() {
1533 let task: TryTrampoline<i32, String> = TryTrampoline::from(Ok(42));
1534 assert_eq!(task.evaluate(), Ok(42));
1535 }
1536
1537 #[test]
1539 fn test_try_task_from_result_err() {
1540 let task: TryTrampoline<i32, String> = TryTrampoline::from(Err("error".to_string()));
1541 assert_eq!(task.evaluate(), Err("error".to_string()));
1542 }
1543
1544 #[test]
1551 fn test_try_trampoline_with_rc() {
1552 use std::rc::Rc;
1553
1554 let task: TryTrampoline<Rc<i32>, Rc<String>> = TryTrampoline::ok(Rc::new(42));
1555 assert_eq!(*task.evaluate().unwrap(), 42);
1556 }
1557
1558 #[test]
1562 fn test_try_trampoline_bind_with_rc() {
1563 use std::rc::Rc;
1564
1565 let task: TryTrampoline<Rc<i32>, Rc<String>> =
1566 TryTrampoline::ok(Rc::new(10)).bind(|rc| TryTrampoline::ok(Rc::new(*rc * 2)));
1567 assert_eq!(*task.evaluate().unwrap(), 20);
1568 }
1569
1570 #[test]
1574 fn test_try_trampoline_tail_rec_m_with_rc() {
1575 use std::rc::Rc;
1576
1577 let task: TryTrampoline<Rc<u64>, Rc<String>> = TryTrampoline::tail_rec_m(
1578 |(n, acc): (u64, Rc<u64>)| {
1579 if n == 0 {
1580 TryTrampoline::ok(ControlFlow::Break(acc))
1581 } else {
1582 TryTrampoline::ok(ControlFlow::Continue((n - 1, Rc::new(*acc + n))))
1583 }
1584 },
1585 (100u64, Rc::new(0u64)),
1586 );
1587 assert_eq!(*task.evaluate().unwrap(), 5050);
1588 }
1589
1590 #[test]
1594 fn test_catch_unwind() {
1595 let task = TryTrampoline::<i32, String>::catch_unwind(|| {
1596 if true {
1597 panic!("oops")
1598 }
1599 42
1600 });
1601 assert_eq!(task.evaluate(), Err("oops".to_string()));
1602 }
1603
1604 #[test]
1608 fn test_catch_unwind_success() {
1609 let task = TryTrampoline::<i32, String>::catch_unwind(|| 42);
1610 assert_eq!(task.evaluate(), Ok(42));
1611 }
1612
1613 #[test]
1617 fn test_catch_unwind_with_panic() {
1618 let task = TryTrampoline::<i32, i32>::catch_unwind_with(
1619 || {
1620 if true {
1621 panic!("oops")
1622 }
1623 42
1624 },
1625 |_payload| -1,
1626 );
1627 assert_eq!(task.evaluate(), Err(-1));
1628 }
1629
1630 #[test]
1634 fn test_catch_unwind_with_success() {
1635 let task = TryTrampoline::<i32, i32>::catch_unwind_with(|| 42, |_payload| -1);
1636 assert_eq!(task.evaluate(), Ok(42));
1637 }
1638
1639 #[quickcheck]
1645 fn functor_identity(x: i32) -> bool {
1646 TryTrampoline::<i32, i32>::ok(x).map(|a| a).evaluate() == Ok(x)
1647 }
1648
1649 #[quickcheck]
1651 fn functor_composition(x: i32) -> bool {
1652 let f = |a: i32| a.wrapping_add(1);
1653 let g = |a: i32| a.wrapping_mul(2);
1654 let lhs = TryTrampoline::<i32, i32>::ok(x).map(move |a| f(g(a))).evaluate();
1655 let rhs = TryTrampoline::<i32, i32>::ok(x).map(g).map(f).evaluate();
1656 lhs == rhs
1657 }
1658
1659 #[quickcheck]
1663 fn monad_left_identity(a: i32) -> bool {
1664 let f = |x: i32| TryTrampoline::<i32, i32>::ok(x.wrapping_mul(2));
1665 TryTrampoline::ok(a).bind(f).evaluate() == f(a).evaluate()
1666 }
1667
1668 #[quickcheck]
1670 fn monad_right_identity(x: i32) -> bool {
1671 TryTrampoline::<i32, i32>::ok(x).bind(TryTrampoline::ok).evaluate() == Ok(x)
1672 }
1673
1674 #[quickcheck]
1676 fn monad_associativity(x: i32) -> bool {
1677 let f = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_add(1));
1678 let g = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_mul(3));
1679 let lhs = TryTrampoline::<i32, i32>::ok(x).bind(f).bind(g).evaluate();
1680 let rhs = TryTrampoline::<i32, i32>::ok(x).bind(move |a| f(a).bind(g)).evaluate();
1681 lhs == rhs
1682 }
1683
1684 #[quickcheck]
1686 fn error_short_circuit(e: i32) -> bool {
1687 TryTrampoline::<i32, i32>::err(e).bind(|x| TryTrampoline::ok(x.wrapping_add(1))).evaluate()
1688 == Err(e)
1689 }
1690
1691 #[test]
1695 fn test_semigroup_append_both_ok() {
1696 use crate::classes::Semigroup;
1697
1698 let a: TryTrampoline<String, ()> = TryTrampoline::ok("hello".to_string());
1699 let b: TryTrampoline<String, ()> = TryTrampoline::ok(" world".to_string());
1700 let result = Semigroup::append(a, b);
1701 assert_eq!(result.evaluate(), Ok("hello world".to_string()));
1702 }
1703
1704 #[test]
1706 fn test_semigroup_append_first_err() {
1707 use crate::classes::Semigroup;
1708
1709 let a: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1710 let b: TryTrampoline<String, String> = TryTrampoline::ok(" world".to_string());
1711 let result = Semigroup::append(a, b);
1712 assert_eq!(result.evaluate(), Err("fail".to_string()));
1713 }
1714
1715 #[test]
1717 fn test_semigroup_append_second_err() {
1718 use crate::classes::Semigroup;
1719
1720 let a: TryTrampoline<String, String> = TryTrampoline::ok("hello".to_string());
1721 let b: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1722 let result = Semigroup::append(a, b);
1723 assert_eq!(result.evaluate(), Err("fail".to_string()));
1724 }
1725
1726 #[quickcheck]
1728 fn semigroup_associativity(
1729 a: String,
1730 b: String,
1731 c: String,
1732 ) -> bool {
1733 use crate::classes::Semigroup;
1734
1735 let lhs = Semigroup::append(
1736 Semigroup::append(
1737 TryTrampoline::<String, ()>::ok(a.clone()),
1738 TryTrampoline::ok(b.clone()),
1739 ),
1740 TryTrampoline::ok(c.clone()),
1741 )
1742 .evaluate();
1743 let rhs = Semigroup::append(
1744 TryTrampoline::<String, ()>::ok(a),
1745 Semigroup::append(TryTrampoline::ok(b), TryTrampoline::ok(c)),
1746 )
1747 .evaluate();
1748 lhs == rhs
1749 }
1750
1751 #[test]
1753 fn test_monoid_empty() {
1754 use crate::classes::Monoid;
1755
1756 let e: TryTrampoline<String, ()> = Monoid::empty();
1757 assert_eq!(e.evaluate(), Ok(String::new()));
1758 }
1759
1760 #[quickcheck]
1762 fn monoid_left_identity(a: String) -> bool {
1763 use crate::classes::{
1764 Monoid,
1765 Semigroup,
1766 };
1767
1768 let lhs = Semigroup::append(Monoid::empty(), TryTrampoline::<String, ()>::ok(a.clone()))
1769 .evaluate();
1770 lhs == Ok(a)
1771 }
1772
1773 #[quickcheck]
1775 fn monoid_right_identity(a: String) -> bool {
1776 use crate::classes::{
1777 Monoid,
1778 Semigroup,
1779 };
1780
1781 let lhs = Semigroup::append(TryTrampoline::<String, ()>::ok(a.clone()), Monoid::empty())
1782 .evaluate();
1783 lhs == Ok(a)
1784 }
1785
1786 #[test]
1790 fn test_bimap_ok() {
1791 let task: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1792 let result = task.bimap(|x| x * 2, |e| e.len());
1793 assert_eq!(result.evaluate(), Ok(20));
1794 }
1795
1796 #[test]
1798 fn test_bimap_err() {
1799 let task: TryTrampoline<i32, String> = TryTrampoline::err("hello".to_string());
1800 let result = task.bimap(|x| x * 2, |e| e.len());
1801 assert_eq!(result.evaluate(), Err(5));
1802 }
1803
1804 #[quickcheck]
1806 fn bimap_consistent_with_map_and_map_err(x: i32) -> bool {
1807 let f = |a: i32| a.wrapping_add(1);
1808 let g = |e: i32| e.wrapping_mul(2);
1809
1810 let via_bimap = TryTrampoline::<i32, i32>::ok(x).bimap(f, g).evaluate();
1811 let via_map = TryTrampoline::<i32, i32>::ok(x).map(f).evaluate();
1812 via_bimap == via_map
1813 }
1814
1815 #[quickcheck]
1817 fn bimap_err_consistent_with_map_err(e: i32) -> bool {
1818 let f = |a: i32| a.wrapping_add(1);
1819 let g = |e: i32| e.wrapping_mul(2);
1820
1821 let via_bimap = TryTrampoline::<i32, i32>::err(e).bimap(f, g).evaluate();
1822 let via_map_err = TryTrampoline::<i32, i32>::err(e).map_err(g).evaluate();
1823 via_bimap == via_map_err
1824 }
1825
1826 #[test]
1833 fn test_into_rc_try_lazy_ok() {
1834 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1835 let lazy = task.into_rc_try_lazy();
1836 assert_eq!(lazy.evaluate(), Ok(&42));
1837 assert_eq!(lazy.evaluate(), Ok(&42));
1839 }
1840
1841 #[test]
1845 fn test_into_rc_try_lazy_err() {
1846 let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1847 let lazy = task.into_rc_try_lazy();
1848 assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1849 }
1850
1851 #[test]
1857 fn test_into_arc_try_lazy_ok() {
1858 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1859 let lazy = task.into_arc_try_lazy();
1860 assert_eq!(lazy.evaluate(), Ok(&42));
1861 assert_eq!(lazy.evaluate(), Ok(&42));
1863 }
1864
1865 #[test]
1869 fn test_into_arc_try_lazy_err() {
1870 let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1871 let lazy = task.into_arc_try_lazy();
1872 assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1873 }
1874
1875 #[test]
1879 fn test_catch_with_stack_safety() {
1880 let n = 100_000u64;
1881 let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1882 for i in 1 ..= n {
1883 task = task.catch_with(move |_| TryTrampoline::err(i));
1884 }
1885 assert_eq!(task.evaluate(), Err(n));
1886 }
1887
1888 #[test]
1893 fn test_catch_with_stack_safety_ok() {
1894 let n = 100_000u64;
1895 let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1896 for i in 1 .. n {
1897 task = task.catch_with(move |_| TryTrampoline::err(i));
1898 }
1899 task = task.catch_with(TryTrampoline::ok);
1900 assert_eq!(task.evaluate(), Ok(n - 1));
1901 }
1902
1903 #[test]
1907 fn test_resume_ok() {
1908 let t: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1909 assert_eq!(t.resume().unwrap(), Ok(42));
1910 }
1911
1912 #[test]
1916 fn test_resume_err() {
1917 let t: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1918 assert_eq!(t.resume().unwrap(), Err("oops".to_string()));
1919 }
1920
1921 #[test]
1926 fn test_resume_deferred() {
1927 let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(99));
1928 match t.resume() {
1929 Ok(_) => panic!("expected suspension"),
1930 Err(thunk) => {
1931 let next = thunk.evaluate();
1932 assert_eq!(next.resume().unwrap(), Ok(99));
1933 }
1934 }
1935 }
1936
1937 #[test]
1941 fn test_resume_chain_reaches_ok() {
1942 let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| {
1943 TryTrampoline::defer(|| TryTrampoline::defer(|| TryTrampoline::ok(7)))
1944 });
1945
1946 let mut current = t;
1947 let mut steps = 0;
1948 loop {
1949 match current.resume() {
1950 Ok(result) => {
1951 assert_eq!(result, Ok(7));
1952 break;
1953 }
1954 Err(thunk) => {
1955 current = thunk.evaluate();
1956 steps += 1;
1957 }
1958 }
1959 }
1960 assert!(steps > 0, "expected at least one suspension step");
1961 }
1962}