1#[fp_macros::document_module]
17mod inner {
18 use {
19 crate::{
20 classes::{
21 Deferrable,
22 LazyConfig,
23 Monoid,
24 Semigroup,
25 TryLazyConfig,
26 },
27 types::{
28 Lazy,
29 Thunk,
30 Trampoline,
31 TryLazy,
32 },
33 },
34 core::ops::ControlFlow,
35 fp_macros::*,
36 std::fmt,
37 };
38
39 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
84 pub struct TryTrampoline<A: 'static, E: 'static>(
86 Trampoline<Result<A, E>>,
88 );
89
90 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
91 #[document_parameters("The fallible trampoline computation.")]
92 impl<A: 'static, E: 'static> TryTrampoline<A, E> {
93 #[document_signature]
95 #[document_parameters("The success value.")]
97 #[document_returns("A `TryTrampoline` representing success.")]
99 #[document_examples]
101 #[inline]
109 pub fn ok(a: A) -> Self {
110 TryTrampoline(Trampoline::pure(Ok(a)))
111 }
112
113 #[document_signature]
118 #[document_parameters("The success value.")]
120 #[document_returns("A `TryTrampoline` representing success.")]
122 #[document_examples]
124 pub fn pure(a: A) -> Self {
132 Self::ok(a)
133 }
134
135 #[document_signature]
137 #[document_returns("The inner `Trampoline<Result<A, E>>`.")]
139 #[document_examples]
141 pub fn into_inner(self) -> Trampoline<Result<A, E>> {
150 self.0
151 }
152
153 #[document_signature]
155 #[document_parameters("The error value.")]
157 #[document_returns("A `TryTrampoline` representing failure.")]
159 #[document_examples]
161 #[inline]
169 pub fn err(e: E) -> Self {
170 TryTrampoline(Trampoline::pure(Err(e)))
171 }
172
173 #[document_signature]
175 #[document_parameters("The closure to execute.")]
177 #[document_returns("A `TryTrampoline` that executes `f` when run.")]
179 #[document_examples]
181 #[inline]
189 pub fn new(f: impl FnOnce() -> Result<A, E> + 'static) -> Self {
190 TryTrampoline(Trampoline::new(f))
191 }
192
193 #[document_signature]
197 #[document_parameters("A thunk that returns the next step.")]
199 #[document_returns("A `TryTrampoline` that executes `f` to get the next step.")]
201 #[document_examples]
203 #[inline]
233 pub fn defer(f: impl FnOnce() -> TryTrampoline<A, E> + 'static) -> Self {
234 TryTrampoline(Trampoline::defer(move || f().0))
235 }
236
237 #[document_signature]
239 #[document_type_parameters("The type of the new success value.")]
241 #[document_parameters("The function to apply to the success value.")]
243 #[document_returns("A new `TryTrampoline` with the transformed success value.")]
245 #[document_examples]
247 #[inline]
255 pub fn map<B: 'static>(
256 self,
257 func: impl FnOnce(A) -> B + 'static,
258 ) -> TryTrampoline<B, E> {
259 TryTrampoline(self.0.map(|result| result.map(func)))
260 }
261
262 #[document_signature]
264 #[document_type_parameters("The type of the new error value.")]
266 #[document_parameters("The function to apply to the error value.")]
268 #[document_returns("A new `TryTrampoline` with the transformed error value.")]
270 #[document_examples]
272 #[inline]
281 pub fn map_err<E2: 'static>(
282 self,
283 func: impl FnOnce(E) -> E2 + 'static,
284 ) -> TryTrampoline<A, E2> {
285 TryTrampoline(self.0.map(|result| result.map_err(func)))
286 }
287
288 #[document_signature]
290 #[document_type_parameters(
292 "The type of the new success value.",
293 "The type of the new error value."
294 )]
295 #[document_parameters(
297 "The function to apply to the success value.",
298 "The function to apply to the error value."
299 )]
300 #[document_returns("A new `TryTrampoline` with both sides transformed.")]
302 #[document_examples]
304 #[inline]
317 pub fn bimap<B: 'static, F: 'static>(
318 self,
319 f: impl FnOnce(A) -> B + 'static,
320 g: impl FnOnce(E) -> F + 'static,
321 ) -> TryTrampoline<B, F> {
322 TryTrampoline(self.0.map(|result| match result {
323 Ok(a) => Ok(f(a)),
324 Err(e) => Err(g(e)),
325 }))
326 }
327
328 #[document_signature]
330 #[document_type_parameters("The type of the new success value.")]
332 #[document_parameters("The function to apply to the success value.")]
334 #[document_returns("A new `TryTrampoline` that chains `f` after this task.")]
336 #[document_examples]
338 #[inline]
346 pub fn bind<B: 'static>(
347 self,
348 f: impl FnOnce(A) -> TryTrampoline<B, E> + 'static,
349 ) -> TryTrampoline<B, E> {
350 TryTrampoline(self.0.bind(|result| match result {
351 Ok(a) => f(a).0,
352 Err(e) => Trampoline::pure(Err(e)),
353 }))
354 }
355
356 #[document_signature]
358 #[document_parameters("The function to apply to the error value.")]
360 #[document_returns("A new `TryTrampoline` that attempts to recover from failure.")]
362 #[document_examples]
364 #[inline]
373 pub fn catch(
374 self,
375 f: impl FnOnce(E) -> TryTrampoline<A, E> + 'static,
376 ) -> Self {
377 TryTrampoline(self.0.bind(|result| match result {
378 Ok(a) => Trampoline::pure(Ok(a)),
379 Err(e) => f(e).0,
380 }))
381 }
382
383 #[document_signature]
390 #[document_type_parameters("The error type produced by the recovery computation.")]
392 #[document_parameters("The monadic recovery function applied to the error value.")]
394 #[document_returns(
396 "A new `TryTrampoline` that either passes through the success value or uses the result of the recovery computation."
397 )]
398 #[document_examples]
400 #[inline]
413 pub fn catch_with<E2: 'static>(
414 self,
415 f: impl FnOnce(E) -> TryTrampoline<A, E2> + 'static,
416 ) -> TryTrampoline<A, E2> {
417 TryTrampoline(self.0.bind(move |result| match result {
418 Ok(a) => Trampoline::pure(Ok(a)),
419 Err(e) => f(e).0,
420 }))
421 }
422
423 #[document_signature]
427 #[document_type_parameters(
429 "The type of the second computation's success value.",
430 "The type of the combined result."
431 )]
432 #[document_parameters("The second computation.", "The function to combine the results.")]
434 #[document_returns("A new `TryTrampoline` producing the combined result.")]
436 #[document_examples]
438 #[inline]
448 pub fn lift2<B: 'static, C: 'static>(
449 self,
450 other: TryTrampoline<B, E>,
451 f: impl FnOnce(A, B) -> C + 'static,
452 ) -> TryTrampoline<C, E> {
453 self.bind(move |a| other.map(move |b| f(a, b)))
454 }
455
456 #[document_signature]
460 #[document_type_parameters("The type of the second computation's success value.")]
462 #[document_parameters("The second computation.")]
464 #[document_returns(
466 "A new `TryTrampoline` that runs both computations and returns the result of the second."
467 )]
468 #[document_examples]
470 #[inline]
480 pub fn then<B: 'static>(
481 self,
482 other: TryTrampoline<B, E>,
483 ) -> TryTrampoline<B, E> {
484 self.bind(move |_| other)
485 }
486
487 #[document_signature]
501 #[document_type_parameters("The type of the state.")]
503 #[document_parameters(
505 "The function that performs one step of the recursion.",
506 "The initial state."
507 )]
508 #[document_returns("A `TryTrampoline` that performs the recursion.")]
510 #[document_examples]
511 pub fn tail_rec_m<S: 'static>(
538 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
539 initial: S,
540 ) -> Self {
541 fn go<S: 'static, A: 'static, E: 'static>(
542 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
543 s: S,
544 ) -> Trampoline<Result<A, E>> {
545 Trampoline::defer(move || {
546 let result = f(s);
547 result.0.bind(move |r| match r {
548 Ok(ControlFlow::Continue(next)) => go(f, next),
549 Ok(ControlFlow::Break(a)) => Trampoline::pure(Ok(a)),
550 Err(e) => Trampoline::pure(Err(e)),
551 })
552 })
553 }
554 TryTrampoline(go(f, initial))
555 }
556
557 #[document_signature]
561 #[document_type_parameters("The type of the state.")]
563 #[document_parameters(
565 "The function that performs one step of the recursion.",
566 "The initial state."
567 )]
568 #[document_returns("A `TryTrampoline` that performs the recursion.")]
570 #[document_examples]
571 pub fn arc_tail_rec_m<S: 'static>(
603 f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + 'static,
604 initial: S,
605 ) -> Self {
606 use std::sync::Arc;
607 let f = Arc::new(f);
608 let wrapper = move |s: S| {
609 let f = Arc::clone(&f);
610 f(s)
611 };
612 Self::tail_rec_m(wrapper, initial)
613 }
614
615 #[document_signature]
617 #[document_returns("The result of the computation.")]
619 #[document_examples]
621 #[inline]
629 pub fn evaluate(self) -> Result<A, E> {
630 self.0.evaluate()
631 }
632
633 #[document_signature]
639 #[document_parameters(
641 "The second `TryTrampoline` whose result will be combined with this one."
642 )]
643 #[document_returns("A new `TryTrampoline` producing the combined result.")]
645 #[document_examples]
647 #[inline]
656 pub fn append(
657 self,
658 other: TryTrampoline<A, E>,
659 ) -> TryTrampoline<A, E>
660 where
661 A: Semigroup + 'static, {
662 self.lift2(other, Semigroup::append)
663 }
664
665 #[document_signature]
667 #[document_returns(
669 "A `TryTrampoline` producing the monoid identity element wrapped in `Ok`."
670 )]
671 #[document_examples]
673 #[inline]
681 pub fn empty() -> TryTrampoline<A, E>
682 where
683 A: Monoid + 'static, {
684 TryTrampoline::ok(Monoid::empty())
685 }
686
687 #[document_signature]
692 #[document_returns(
694 "A memoized `RcTryLazy` value that evaluates this trampoline on first access."
695 )]
696 #[document_examples]
698 #[inline]
707 pub fn into_rc_try_lazy(self) -> crate::types::RcTryLazy<'static, A, E> {
708 crate::types::RcTryLazy::from(self)
709 }
710
711 #[document_signature]
716 #[document_returns("A thread-safe `ArcTryLazy` containing the eagerly evaluated result.")]
718 #[document_examples]
720 #[inline]
729 pub fn into_arc_try_lazy(self) -> crate::types::ArcTryLazy<'static, A, E>
730 where
731 A: Send + Sync,
732 E: Send + Sync, {
733 crate::types::ArcTryLazy::from(self)
734 }
735
736 #[document_signature]
747 #[document_returns(
749 "`Ok(result)` if the computation is finished, `Err(thunk)` if it is suspended."
750 )]
751 #[document_examples]
753 pub fn resume(self) -> Result<Result<A, E>, Thunk<'static, TryTrampoline<A, E>>> {
772 match self.0.resume() {
773 Ok(result) => Ok(result),
774 Err(thunk) => Err(thunk.map(TryTrampoline)),
775 }
776 }
777 }
778
779 #[document_type_parameters("The type of the computed value.", "The type of the error value.")]
780 impl<A: 'static, E: 'static> TryTrampoline<A, E> {
781 #[document_signature]
788 #[document_parameters(
790 "The closure that might panic.",
791 "The function that converts a panic payload into the error type."
792 )]
793 #[document_returns(
795 "A new `TryTrampoline` instance where panics are converted to `Err(E)` via the handler."
796 )]
797 #[document_examples]
799 pub fn catch_unwind_with(
815 f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static,
816 handler: impl FnOnce(Box<dyn std::any::Any + Send>) -> E + 'static,
817 ) -> Self {
818 Self::new(move || std::panic::catch_unwind(f).map_err(handler))
819 }
820 }
821
822 #[document_type_parameters("The type of the computed value.")]
823 impl<A: 'static> TryTrampoline<A, String> {
824 #[document_signature]
833 #[document_parameters("The closure that might panic.")]
835 #[document_returns(
837 "A new `TryTrampoline` instance where panics are converted to `Err(String)`."
838 )]
839 #[document_examples]
841 pub fn catch_unwind(f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static) -> Self {
854 Self::catch_unwind_with(f, crate::utils::panic_payload_to_string)
855 }
856 }
857
858 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
859 impl<A, E> From<Trampoline<A>> for TryTrampoline<A, E>
860 where
861 A: 'static,
862 E: 'static,
863 {
864 #[document_signature]
865 #[document_parameters("The trampoline computation to convert.")]
866 #[document_returns("A new `TryTrampoline` instance that wraps the trampoline.")]
867 #[document_examples]
868 fn from(task: Trampoline<A>) -> Self {
876 TryTrampoline(task.map(Ok))
877 }
878 }
879
880 #[document_type_parameters(
881 "The type of the success value.",
882 "The type of the error value.",
883 "The memoization configuration."
884 )]
885 impl<A, E, Config> From<Lazy<'static, A, Config>> for TryTrampoline<A, E>
886 where
887 A: Clone + 'static,
888 E: 'static,
889 Config: LazyConfig,
890 {
891 #[document_signature]
897 #[document_parameters("The lazy value to convert.")]
898 #[document_returns("A new `TryTrampoline` instance that wraps the lazy value.")]
899 #[document_examples]
900 fn from(memo: Lazy<'static, A, Config>) -> Self {
908 TryTrampoline(Trampoline::new(move || Ok(memo.evaluate().clone())))
909 }
910 }
911
912 #[document_type_parameters(
913 "The type of the success value.",
914 "The type of the error value.",
915 "The memoization configuration."
916 )]
917 impl<A, E, Config> From<TryLazy<'static, A, E, Config>> for TryTrampoline<A, E>
918 where
919 A: Clone + 'static,
920 E: Clone + 'static,
921 Config: TryLazyConfig,
922 {
923 #[document_signature]
928 #[document_parameters("The fallible lazy value to convert.")]
929 #[document_returns("A new `TryTrampoline` instance that wraps the fallible lazy value.")]
930 #[document_examples]
931 fn from(memo: TryLazy<'static, A, E, Config>) -> Self {
939 TryTrampoline(Trampoline::new(move || {
940 let result = memo.evaluate();
941 match result {
942 Ok(a) => Ok(a.clone()),
943 Err(e) => Err(e.clone()),
944 }
945 }))
946 }
947 }
948
949 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
950 impl<A, E> From<crate::types::TryThunk<'static, A, E>> for TryTrampoline<A, E>
951 where
952 A: 'static,
953 E: 'static,
954 {
955 #[document_signature]
960 #[document_parameters("The fallible thunk to convert.")]
961 #[document_returns("A new `TryTrampoline` instance that evaluates the thunk.")]
962 #[document_examples]
963 fn from(thunk: crate::types::TryThunk<'static, A, E>) -> Self {
971 TryTrampoline::new(move || thunk.evaluate())
972 }
973 }
974
975 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
976 impl<A, E> From<Result<A, E>> for TryTrampoline<A, E>
977 where
978 A: 'static,
979 E: 'static,
980 {
981 #[document_signature]
982 #[document_parameters("The result to convert.")]
983 #[document_returns("A new `TryTrampoline` instance that produces the result.")]
984 #[document_examples]
985 fn from(result: Result<A, E>) -> Self {
995 TryTrampoline(Trampoline::pure(result))
996 }
997 }
998
999 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1000 impl<A, E> Deferrable<'static> for TryTrampoline<A, E>
1001 where
1002 A: 'static,
1003 E: 'static,
1004 {
1005 #[document_signature]
1007 #[document_parameters("A thunk that produces the value.")]
1009 #[document_returns("The deferred value.")]
1011 #[document_examples]
1013 fn defer(f: impl FnOnce() -> Self + 'static) -> Self
1026 where
1027 Self: Sized, {
1028 TryTrampoline(Trampoline::defer(move || f().0))
1029 }
1030 }
1031
1032 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1033 impl<A, E> Semigroup for TryTrampoline<A, E>
1034 where
1035 A: Semigroup + 'static,
1036 E: 'static,
1037 {
1038 #[document_signature]
1043 #[document_parameters(
1045 "The first `TryTrampoline` computation.",
1046 "The second `TryTrampoline` computation."
1047 )]
1048 #[document_returns(
1050 "A `TryTrampoline` that evaluates both and combines the results, or propagates the first error."
1051 )]
1052 #[document_examples]
1054 fn append(
1067 a: Self,
1068 b: Self,
1069 ) -> Self {
1070 a.lift2(b, Semigroup::append)
1071 }
1072 }
1073
1074 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1075 impl<A, E> Monoid for TryTrampoline<A, E>
1076 where
1077 A: Monoid + 'static,
1078 E: 'static,
1079 {
1080 #[document_signature]
1082 #[document_returns("A `TryTrampoline` that succeeds with the monoidal identity element.")]
1084 #[document_examples]
1086 fn empty() -> Self {
1097 TryTrampoline::ok(A::empty())
1098 }
1099 }
1100
1101 #[document_type_parameters("The type of the success value.", "The type of the error value.")]
1102 #[document_parameters("The try-trampoline to format.")]
1103 impl<A: 'static, E: 'static> fmt::Debug for TryTrampoline<A, E> {
1104 #[document_signature]
1106 #[document_parameters("The formatter.")]
1107 #[document_returns("The formatting result.")]
1108 #[document_examples]
1109 fn fmt(
1116 &self,
1117 f: &mut fmt::Formatter<'_>,
1118 ) -> fmt::Result {
1119 f.write_str("TryTrampoline(<unevaluated>)")
1120 }
1121 }
1122}
1123pub use inner::*;
1124
1125#[cfg(test)]
1126mod tests {
1127 use {
1128 super::*,
1129 crate::types::Trampoline,
1130 core::ops::ControlFlow,
1131 quickcheck_macros::quickcheck,
1132 };
1133
1134 #[test]
1138 fn test_try_task_ok() {
1139 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1140 assert_eq!(task.evaluate(), Ok(42));
1141 }
1142
1143 #[test]
1147 fn test_try_task_err() {
1148 let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
1149 assert_eq!(task.evaluate(), Err("error".to_string()));
1150 }
1151
1152 #[test]
1156 fn test_try_task_map() {
1157 let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
1158 assert_eq!(task.evaluate(), Ok(20));
1159 }
1160
1161 #[test]
1165 fn test_try_task_map_err() {
1166 let task: TryTrampoline<i32, String> =
1167 TryTrampoline::err("error".to_string()).map_err(|e| e.to_uppercase());
1168 assert_eq!(task.evaluate(), Err("ERROR".to_string()));
1169 }
1170
1171 #[test]
1175 fn test_try_task_bind() {
1176 let task: TryTrampoline<i32, String> =
1177 TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
1178 assert_eq!(task.evaluate(), Ok(20));
1179 }
1180
1181 #[test]
1185 fn test_try_task_or_else() {
1186 let task: TryTrampoline<i32, String> =
1187 TryTrampoline::err("error".to_string()).catch(|_| TryTrampoline::ok(42));
1188 assert_eq!(task.evaluate(), Ok(42));
1189 }
1190
1191 #[test]
1195 fn test_catch_with_recovers() {
1196 let task: TryTrampoline<i32, i32> = TryTrampoline::<i32, String>::err("error".to_string())
1197 .catch_with(|_| TryTrampoline::err(42));
1198 assert_eq!(task.evaluate(), Err(42));
1199 }
1200
1201 #[test]
1205 fn test_catch_with_success_passes_through() {
1206 let task: TryTrampoline<i32, i32> =
1207 TryTrampoline::<i32, String>::ok(1).catch_with(|_| TryTrampoline::err(42));
1208 assert_eq!(task.evaluate(), Ok(1));
1209 }
1210
1211 #[test]
1215 fn test_try_task_new() {
1216 let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
1217 assert_eq!(task.evaluate(), Ok(42));
1218 }
1219
1220 #[test]
1224 fn test_try_trampoline_pure() {
1225 let task: TryTrampoline<i32, String> = TryTrampoline::pure(42);
1226 assert_eq!(task.evaluate(), Ok(42));
1227 }
1228
1229 #[test]
1233 fn test_try_trampoline_into_inner() {
1234 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1235 let inner: Trampoline<Result<i32, String>> = task.into_inner();
1236 assert_eq!(inner.evaluate(), Ok(42));
1237 }
1238
1239 #[test]
1241 fn test_try_task_from_task() {
1242 let task = Trampoline::pure(42);
1243 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(task);
1244 assert_eq!(try_task.evaluate(), Ok(42));
1245 }
1246
1247 #[test]
1249 fn test_try_task_from_memo() {
1250 use crate::types::ArcLazy;
1251 let memo = ArcLazy::new(|| 42);
1252 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1253 assert_eq!(try_task.evaluate(), Ok(42));
1254 }
1255
1256 #[test]
1258 fn test_try_task_from_try_memo() {
1259 use crate::types::ArcTryLazy;
1260 let memo = ArcTryLazy::new(|| Ok(42));
1261 let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1262 assert_eq!(try_task.evaluate(), Ok(42));
1263 }
1264
1265 #[test]
1269 fn test_try_task_lift2_success() {
1270 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1271 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1272 let t3 = t1.lift2(t2, |a, b| a + b);
1273 assert_eq!(t3.evaluate(), Ok(30));
1274 }
1275
1276 #[test]
1280 fn test_try_task_lift2_first_error() {
1281 let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1282 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1283 let t3 = t1.lift2(t2, |a, b| a + b);
1284 assert_eq!(t3.evaluate(), Err("first".to_string()));
1285 }
1286
1287 #[test]
1291 fn test_try_task_lift2_second_error() {
1292 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1293 let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1294 let t3 = t1.lift2(t2, |a, b| a + b);
1295 assert_eq!(t3.evaluate(), Err("second".to_string()));
1296 }
1297
1298 #[test]
1302 fn test_try_task_then_success() {
1303 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1304 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1305 let t3 = t1.then(t2);
1306 assert_eq!(t3.evaluate(), Ok(20));
1307 }
1308
1309 #[test]
1313 fn test_try_task_then_first_error() {
1314 let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1315 let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1316 let t3 = t1.then(t2);
1317 assert_eq!(t3.evaluate(), Err("first".to_string()));
1318 }
1319
1320 #[test]
1324 fn test_try_task_then_second_error() {
1325 let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1326 let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1327 let t3 = t1.then(t2);
1328 assert_eq!(t3.evaluate(), Err("second".to_string()));
1329 }
1330
1331 #[test]
1335 fn test_try_task_tail_rec_m() {
1336 fn factorial(n: i32) -> TryTrampoline<i32, String> {
1337 TryTrampoline::tail_rec_m(
1338 |(n, acc)| {
1339 if n < 0 {
1340 TryTrampoline::err("Negative input".to_string())
1341 } else if n <= 1 {
1342 TryTrampoline::ok(ControlFlow::Break(acc))
1343 } else {
1344 TryTrampoline::ok(ControlFlow::Continue((n - 1, n * acc)))
1345 }
1346 },
1347 (n, 1),
1348 )
1349 }
1350
1351 assert_eq!(factorial(5).evaluate(), Ok(120));
1352 assert_eq!(factorial(0).evaluate(), Ok(1));
1353 assert_eq!(factorial(1).evaluate(), Ok(1));
1354 }
1355
1356 #[test]
1360 fn test_try_task_tail_rec_m_error() {
1361 let task: TryTrampoline<i32, String> = TryTrampoline::tail_rec_m(
1362 |n: i32| {
1363 if n >= 5 {
1364 TryTrampoline::err(format!("too large: {}", n))
1365 } else {
1366 TryTrampoline::ok(ControlFlow::Continue(n + 1))
1367 }
1368 },
1369 0,
1370 );
1371
1372 assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1373 }
1374
1375 #[test]
1379 fn test_try_task_tail_rec_m_stack_safety() {
1380 let n = 100_000u64;
1381 let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1382 |(remaining, acc)| {
1383 if remaining == 0 {
1384 TryTrampoline::ok(ControlFlow::Break(acc))
1385 } else {
1386 TryTrampoline::ok(ControlFlow::Continue((remaining - 1, acc + remaining)))
1387 }
1388 },
1389 (n, 0u64),
1390 );
1391
1392 assert_eq!(task.evaluate(), Ok(n * (n + 1) / 2));
1393 }
1394
1395 #[test]
1399 fn test_try_task_tail_rec_m_stack_safety_error() {
1400 let n = 100_000u64;
1401 let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1402 |remaining| {
1403 if remaining == 0 {
1404 TryTrampoline::err("done iterating".to_string())
1405 } else {
1406 TryTrampoline::ok(ControlFlow::Continue(remaining - 1))
1407 }
1408 },
1409 n,
1410 );
1411
1412 assert_eq!(task.evaluate(), Err("done iterating".to_string()));
1413 }
1414
1415 #[test]
1419 fn test_try_task_arc_tail_rec_m() {
1420 use std::sync::{
1421 Arc,
1422 atomic::{
1423 AtomicUsize,
1424 Ordering,
1425 },
1426 };
1427
1428 let counter = Arc::new(AtomicUsize::new(0));
1429 let counter_clone = Arc::clone(&counter);
1430
1431 let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1432 move |n| {
1433 counter_clone.fetch_add(1, Ordering::SeqCst);
1434 if n == 0 {
1435 TryTrampoline::ok(ControlFlow::Break(0))
1436 } else {
1437 TryTrampoline::ok(ControlFlow::Continue(n - 1))
1438 }
1439 },
1440 10,
1441 );
1442
1443 assert_eq!(task.evaluate(), Ok(0));
1444 assert_eq!(counter.load(Ordering::SeqCst), 11);
1445 }
1446
1447 #[test]
1451 fn test_try_task_arc_tail_rec_m_error() {
1452 use std::sync::{
1453 Arc,
1454 atomic::{
1455 AtomicUsize,
1456 Ordering,
1457 },
1458 };
1459
1460 let counter = Arc::new(AtomicUsize::new(0));
1461 let counter_clone = Arc::clone(&counter);
1462
1463 let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1464 move |n| {
1465 counter_clone.fetch_add(1, Ordering::SeqCst);
1466 if n >= 5 {
1467 TryTrampoline::err(format!("too large: {}", n))
1468 } else {
1469 TryTrampoline::ok(ControlFlow::Continue(n + 1))
1470 }
1471 },
1472 0,
1473 );
1474
1475 assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1476 assert_eq!(counter.load(Ordering::SeqCst), 6);
1478 }
1479
1480 #[test]
1482 fn test_try_task_from_try_thunk_ok() {
1483 use crate::types::TryThunk;
1484 let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1485 let task = TryTrampoline::from(thunk);
1486 assert_eq!(task.evaluate(), Ok(42));
1487 }
1488
1489 #[test]
1491 fn test_try_task_from_try_thunk_err() {
1492 use crate::types::TryThunk;
1493 let thunk = TryThunk::new(|| Err::<i32, String>("error".to_string()));
1494 let task = TryTrampoline::from(thunk);
1495 assert_eq!(task.evaluate(), Err("error".to_string()));
1496 }
1497
1498 #[test]
1502 fn test_try_thunk_try_trampoline_round_trip() {
1503 use crate::types::TryThunk;
1504
1505 let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1507 let tramp = TryTrampoline::from(thunk);
1508 let thunk_back: TryThunk<i32, String> = TryThunk::from(tramp);
1509 assert_eq!(thunk_back.evaluate(), Ok(42));
1510
1511 let tramp = TryTrampoline::err("fail".to_string());
1513 let thunk: TryThunk<i32, String> = TryThunk::from(tramp);
1514 let tramp_back = TryTrampoline::from(thunk);
1515 assert_eq!(tramp_back.evaluate(), Err("fail".to_string()));
1516 }
1517
1518 #[test]
1520 fn test_try_task_from_result_ok() {
1521 let task: TryTrampoline<i32, String> = TryTrampoline::from(Ok(42));
1522 assert_eq!(task.evaluate(), Ok(42));
1523 }
1524
1525 #[test]
1527 fn test_try_task_from_result_err() {
1528 let task: TryTrampoline<i32, String> = TryTrampoline::from(Err("error".to_string()));
1529 assert_eq!(task.evaluate(), Err("error".to_string()));
1530 }
1531
1532 #[test]
1539 fn test_try_trampoline_with_rc() {
1540 use std::rc::Rc;
1541
1542 let task: TryTrampoline<Rc<i32>, Rc<String>> = TryTrampoline::ok(Rc::new(42));
1543 assert_eq!(*task.evaluate().unwrap(), 42);
1544 }
1545
1546 #[test]
1550 fn test_try_trampoline_bind_with_rc() {
1551 use std::rc::Rc;
1552
1553 let task: TryTrampoline<Rc<i32>, Rc<String>> =
1554 TryTrampoline::ok(Rc::new(10)).bind(|rc| TryTrampoline::ok(Rc::new(*rc * 2)));
1555 assert_eq!(*task.evaluate().unwrap(), 20);
1556 }
1557
1558 #[test]
1562 fn test_try_trampoline_tail_rec_m_with_rc() {
1563 use std::rc::Rc;
1564
1565 let task: TryTrampoline<Rc<u64>, Rc<String>> = TryTrampoline::tail_rec_m(
1566 |(n, acc): (u64, Rc<u64>)| {
1567 if n == 0 {
1568 TryTrampoline::ok(ControlFlow::Break(acc))
1569 } else {
1570 TryTrampoline::ok(ControlFlow::Continue((n - 1, Rc::new(*acc + n))))
1571 }
1572 },
1573 (100u64, Rc::new(0u64)),
1574 );
1575 assert_eq!(*task.evaluate().unwrap(), 5050);
1576 }
1577
1578 #[test]
1582 fn test_catch_unwind() {
1583 let task = TryTrampoline::<i32, String>::catch_unwind(|| {
1584 if true {
1585 panic!("oops")
1586 }
1587 42
1588 });
1589 assert_eq!(task.evaluate(), Err("oops".to_string()));
1590 }
1591
1592 #[test]
1596 fn test_catch_unwind_success() {
1597 let task = TryTrampoline::<i32, String>::catch_unwind(|| 42);
1598 assert_eq!(task.evaluate(), Ok(42));
1599 }
1600
1601 #[test]
1605 fn test_catch_unwind_with_panic() {
1606 let task = TryTrampoline::<i32, i32>::catch_unwind_with(
1607 || {
1608 if true {
1609 panic!("oops")
1610 }
1611 42
1612 },
1613 |_payload| -1,
1614 );
1615 assert_eq!(task.evaluate(), Err(-1));
1616 }
1617
1618 #[test]
1622 fn test_catch_unwind_with_success() {
1623 let task = TryTrampoline::<i32, i32>::catch_unwind_with(|| 42, |_payload| -1);
1624 assert_eq!(task.evaluate(), Ok(42));
1625 }
1626
1627 #[quickcheck]
1633 fn functor_identity(x: i32) -> bool {
1634 TryTrampoline::<i32, i32>::ok(x).map(|a| a).evaluate() == Ok(x)
1635 }
1636
1637 #[quickcheck]
1639 fn functor_composition(x: i32) -> bool {
1640 let f = |a: i32| a.wrapping_add(1);
1641 let g = |a: i32| a.wrapping_mul(2);
1642 let lhs = TryTrampoline::<i32, i32>::ok(x).map(move |a| f(g(a))).evaluate();
1643 let rhs = TryTrampoline::<i32, i32>::ok(x).map(g).map(f).evaluate();
1644 lhs == rhs
1645 }
1646
1647 #[quickcheck]
1651 fn monad_left_identity(a: i32) -> bool {
1652 let f = |x: i32| TryTrampoline::<i32, i32>::ok(x.wrapping_mul(2));
1653 TryTrampoline::ok(a).bind(f).evaluate() == f(a).evaluate()
1654 }
1655
1656 #[quickcheck]
1658 fn monad_right_identity(x: i32) -> bool {
1659 TryTrampoline::<i32, i32>::ok(x).bind(TryTrampoline::ok).evaluate() == Ok(x)
1660 }
1661
1662 #[quickcheck]
1664 fn monad_associativity(x: i32) -> bool {
1665 let f = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_add(1));
1666 let g = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_mul(3));
1667 let lhs = TryTrampoline::<i32, i32>::ok(x).bind(f).bind(g).evaluate();
1668 let rhs = TryTrampoline::<i32, i32>::ok(x).bind(move |a| f(a).bind(g)).evaluate();
1669 lhs == rhs
1670 }
1671
1672 #[quickcheck]
1674 fn error_short_circuit(e: i32) -> bool {
1675 TryTrampoline::<i32, i32>::err(e).bind(|x| TryTrampoline::ok(x.wrapping_add(1))).evaluate()
1676 == Err(e)
1677 }
1678
1679 #[test]
1683 fn test_semigroup_append_both_ok() {
1684 use crate::classes::Semigroup;
1685
1686 let a: TryTrampoline<String, ()> = TryTrampoline::ok("hello".to_string());
1687 let b: TryTrampoline<String, ()> = TryTrampoline::ok(" world".to_string());
1688 let result = Semigroup::append(a, b);
1689 assert_eq!(result.evaluate(), Ok("hello world".to_string()));
1690 }
1691
1692 #[test]
1694 fn test_semigroup_append_first_err() {
1695 use crate::classes::Semigroup;
1696
1697 let a: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1698 let b: TryTrampoline<String, String> = TryTrampoline::ok(" world".to_string());
1699 let result = Semigroup::append(a, b);
1700 assert_eq!(result.evaluate(), Err("fail".to_string()));
1701 }
1702
1703 #[test]
1705 fn test_semigroup_append_second_err() {
1706 use crate::classes::Semigroup;
1707
1708 let a: TryTrampoline<String, String> = TryTrampoline::ok("hello".to_string());
1709 let b: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1710 let result = Semigroup::append(a, b);
1711 assert_eq!(result.evaluate(), Err("fail".to_string()));
1712 }
1713
1714 #[quickcheck]
1716 fn semigroup_associativity(
1717 a: String,
1718 b: String,
1719 c: String,
1720 ) -> bool {
1721 use crate::classes::Semigroup;
1722
1723 let lhs = Semigroup::append(
1724 Semigroup::append(
1725 TryTrampoline::<String, ()>::ok(a.clone()),
1726 TryTrampoline::ok(b.clone()),
1727 ),
1728 TryTrampoline::ok(c.clone()),
1729 )
1730 .evaluate();
1731 let rhs = Semigroup::append(
1732 TryTrampoline::<String, ()>::ok(a),
1733 Semigroup::append(TryTrampoline::ok(b), TryTrampoline::ok(c)),
1734 )
1735 .evaluate();
1736 lhs == rhs
1737 }
1738
1739 #[test]
1741 fn test_monoid_empty() {
1742 use crate::classes::Monoid;
1743
1744 let e: TryTrampoline<String, ()> = Monoid::empty();
1745 assert_eq!(e.evaluate(), Ok(String::new()));
1746 }
1747
1748 #[quickcheck]
1750 fn monoid_left_identity(a: String) -> bool {
1751 use crate::classes::{
1752 Monoid,
1753 Semigroup,
1754 };
1755
1756 let lhs = Semigroup::append(Monoid::empty(), TryTrampoline::<String, ()>::ok(a.clone()))
1757 .evaluate();
1758 lhs == Ok(a)
1759 }
1760
1761 #[quickcheck]
1763 fn monoid_right_identity(a: String) -> bool {
1764 use crate::classes::{
1765 Monoid,
1766 Semigroup,
1767 };
1768
1769 let lhs = Semigroup::append(TryTrampoline::<String, ()>::ok(a.clone()), Monoid::empty())
1770 .evaluate();
1771 lhs == Ok(a)
1772 }
1773
1774 #[test]
1778 fn test_bimap_ok() {
1779 let task: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1780 let result = task.bimap(|x| x * 2, |e| e.len());
1781 assert_eq!(result.evaluate(), Ok(20));
1782 }
1783
1784 #[test]
1786 fn test_bimap_err() {
1787 let task: TryTrampoline<i32, String> = TryTrampoline::err("hello".to_string());
1788 let result = task.bimap(|x| x * 2, |e| e.len());
1789 assert_eq!(result.evaluate(), Err(5));
1790 }
1791
1792 #[quickcheck]
1794 fn bimap_consistent_with_map_and_map_err(x: i32) -> bool {
1795 let f = |a: i32| a.wrapping_add(1);
1796 let g = |e: i32| e.wrapping_mul(2);
1797
1798 let via_bimap = TryTrampoline::<i32, i32>::ok(x).bimap(f, g).evaluate();
1799 let via_map = TryTrampoline::<i32, i32>::ok(x).map(f).evaluate();
1800 via_bimap == via_map
1801 }
1802
1803 #[quickcheck]
1805 fn bimap_err_consistent_with_map_err(e: i32) -> bool {
1806 let f = |a: i32| a.wrapping_add(1);
1807 let g = |e: i32| e.wrapping_mul(2);
1808
1809 let via_bimap = TryTrampoline::<i32, i32>::err(e).bimap(f, g).evaluate();
1810 let via_map_err = TryTrampoline::<i32, i32>::err(e).map_err(g).evaluate();
1811 via_bimap == via_map_err
1812 }
1813
1814 #[test]
1821 fn test_into_rc_try_lazy_ok() {
1822 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1823 let lazy = task.into_rc_try_lazy();
1824 assert_eq!(lazy.evaluate(), Ok(&42));
1825 assert_eq!(lazy.evaluate(), Ok(&42));
1827 }
1828
1829 #[test]
1833 fn test_into_rc_try_lazy_err() {
1834 let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1835 let lazy = task.into_rc_try_lazy();
1836 assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1837 }
1838
1839 #[test]
1845 fn test_into_arc_try_lazy_ok() {
1846 let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1847 let lazy = task.into_arc_try_lazy();
1848 assert_eq!(lazy.evaluate(), Ok(&42));
1849 assert_eq!(lazy.evaluate(), Ok(&42));
1851 }
1852
1853 #[test]
1857 fn test_into_arc_try_lazy_err() {
1858 let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1859 let lazy = task.into_arc_try_lazy();
1860 assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1861 }
1862
1863 #[test]
1867 fn test_catch_with_stack_safety() {
1868 let n = 100_000u64;
1869 let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1870 for i in 1 ..= n {
1871 task = task.catch_with(move |_| TryTrampoline::err(i));
1872 }
1873 assert_eq!(task.evaluate(), Err(n));
1874 }
1875
1876 #[test]
1881 fn test_catch_with_stack_safety_ok() {
1882 let n = 100_000u64;
1883 let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1884 for i in 1 .. n {
1885 task = task.catch_with(move |_| TryTrampoline::err(i));
1886 }
1887 task = task.catch_with(|e| TryTrampoline::ok(e));
1888 assert_eq!(task.evaluate(), Ok(n - 1));
1889 }
1890
1891 #[test]
1895 fn test_resume_ok() {
1896 let t: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1897 assert_eq!(t.resume().unwrap(), Ok(42));
1898 }
1899
1900 #[test]
1904 fn test_resume_err() {
1905 let t: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1906 assert_eq!(t.resume().unwrap(), Err("oops".to_string()));
1907 }
1908
1909 #[test]
1914 fn test_resume_deferred() {
1915 let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(99));
1916 match t.resume() {
1917 Ok(_) => panic!("expected suspension"),
1918 Err(thunk) => {
1919 let next = thunk.evaluate();
1920 assert_eq!(next.resume().unwrap(), Ok(99));
1921 }
1922 }
1923 }
1924
1925 #[test]
1929 fn test_resume_chain_reaches_ok() {
1930 let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| {
1931 TryTrampoline::defer(|| TryTrampoline::defer(|| TryTrampoline::ok(7)))
1932 });
1933
1934 let mut current = t;
1935 let mut steps = 0;
1936 loop {
1937 match current.resume() {
1938 Ok(result) => {
1939 assert_eq!(result, Ok(7));
1940 break;
1941 }
1942 Err(thunk) => {
1943 current = thunk.evaluate();
1944 steps += 1;
1945 }
1946 }
1947 }
1948 assert!(steps > 0, "expected at least one suspension step");
1949 }
1950}