1#[fp_macros::document_module]
6mod inner {
7 use {
8 crate::{
9 Apply,
10 brands::ThunkBrand,
11 classes::*,
12 impl_kind,
13 kinds::*,
14 types::{
15 ArcLazyConfig,
16 Lazy,
17 RcLazyConfig,
18 SendThunk,
19 Trampoline,
20 },
21 },
22 core::ops::ControlFlow,
23 fp_macros::*,
24 std::fmt,
25 };
26
27 #[document_type_parameters(
85 "The lifetime of the computation.",
86 "The type of the value produced by the computation."
87 )]
88 pub struct Thunk<'a, A>(
90 Box<dyn FnOnce() -> A + 'a>,
92 );
93
94 #[document_type_parameters(
95 "The lifetime of the computation.",
96 "The type of the value produced by the computation."
97 )]
98 #[document_parameters("The thunk instance.")]
99 impl<'a, A: 'a> Thunk<'a, A> {
100 #[document_signature]
102 #[document_parameters("The thunk to wrap.")]
104 #[document_returns("A new `Thunk` instance.")]
106 #[document_examples]
108 #[inline]
116 pub fn new(f: impl FnOnce() -> A + 'a) -> Self {
117 Thunk(Box::new(f))
118 }
119
120 #[document_signature]
122 #[document_parameters("The value to wrap.")]
124 #[document_returns("A new `Thunk` instance containing the value.")]
126 #[document_examples]
128 #[inline]
140 pub fn pure(a: A) -> Self {
141 Thunk::new(move || a)
142 }
143
144 #[document_signature]
146 #[document_parameters("The thunk that returns a `Thunk`.")]
148 #[document_returns("A new `Thunk` instance.")]
150 #[document_examples]
152 #[inline]
164 pub fn defer(f: impl FnOnce() -> Thunk<'a, A> + 'a) -> Self {
165 Thunk::new(move || f().evaluate())
166 }
167
168 #[document_signature]
178 #[document_type_parameters("The type of the result of the new computation.")]
180 #[document_parameters("The function to apply to the result of the computation.")]
182 #[document_returns("A new `Thunk` instance representing the chained computation.")]
184 #[document_examples]
186 #[inline]
197 pub fn bind<B: 'a>(
198 self,
199 f: impl FnOnce(A) -> Thunk<'a, B> + 'a,
200 ) -> Thunk<'a, B> {
201 Thunk::new(move || {
202 let a = (self.0)();
203 let thunk_b = f(a);
204 (thunk_b.0)()
205 })
206 }
207
208 #[document_signature]
217 #[document_type_parameters("The type of the result of the transformation.")]
219 #[document_parameters("The function to apply to the result of the computation.")]
221 #[document_returns("A new `Thunk` instance with the transformed result.")]
223 #[document_examples]
225 #[inline]
236 pub fn map<B: 'a>(
237 self,
238 f: impl FnOnce(A) -> B + 'a,
239 ) -> Thunk<'a, B> {
240 Thunk::new(move || f((self.0)()))
241 }
242
243 #[document_signature]
245 #[document_returns("The result of the computation.")]
247 #[document_examples]
249 #[inline]
260 pub fn evaluate(self) -> A {
261 (self.0)()
262 }
263
264 #[document_signature]
269 #[document_returns("A memoized `Lazy` value that evaluates this thunk on first access.")]
271 #[document_examples]
273 #[inline]
282 pub fn into_rc_lazy(self) -> Lazy<'a, A, RcLazyConfig> {
283 Lazy::from(self)
284 }
285
286 #[document_signature]
294 #[document_returns("A thread-safe `ArcLazy` containing the eagerly evaluated result.")]
296 #[document_examples]
298 #[inline]
307 pub fn into_arc_lazy(self) -> Lazy<'a, A, ArcLazyConfig>
308 where
309 A: Send + Sync + 'a, {
310 let val = self.evaluate();
311 Lazy::<'a, A, ArcLazyConfig>::new(move || val)
312 }
313 }
314
315 #[document_type_parameters(
316 "The lifetime of the computation.",
317 "The type of the value produced by the computation.",
318 "The memoization configuration."
319 )]
320 impl<'a, A, Config> From<Lazy<'a, A, Config>> for Thunk<'a, A>
321 where
322 A: Clone + 'a,
323 Config: LazyConfig,
324 {
325 #[document_signature]
330 #[document_parameters("The lazy value to convert.")]
331 #[document_returns("A thunk that evaluates the lazy value.")]
332 #[document_examples]
333 fn from(lazy: Lazy<'a, A, Config>) -> Self {
341 Thunk::new(move || lazy.evaluate().clone())
342 }
343 }
344
345 #[document_type_parameters(
346 "The lifetime of the computation.",
347 "The type of the value produced by the computation."
348 )]
349 impl<'a, A: 'a> From<SendThunk<'a, A>> for Thunk<'a, A> {
350 #[document_signature]
357 #[document_parameters("The send thunk to convert.")]
358 #[document_returns("A `Thunk` wrapping the same deferred computation.")]
359 #[document_examples]
360 fn from(send_thunk: SendThunk<'a, A>) -> Self {
368 Thunk(send_thunk.into_inner())
369 }
370 }
371
372 #[document_type_parameters("The type of the value produced by the computation.")]
373 impl<A: 'static> From<crate::types::Trampoline<A>> for Thunk<'static, A> {
374 #[document_signature]
375 #[document_parameters("The trampoline to convert.")]
376 #[document_returns("A thunk that evaluates the trampoline.")]
377 #[document_examples]
378 fn from(trampoline: crate::types::Trampoline<A>) -> Self {
386 Thunk::new(move || trampoline.evaluate())
387 }
388 }
389
390 #[document_type_parameters("The type of the value produced by the computation.")]
391 impl<A: 'static> From<Thunk<'static, A>> for Trampoline<A> {
392 #[document_signature]
397 #[document_parameters("The thunk to convert.")]
398 #[document_returns("A trampoline that evaluates the thunk.")]
399 #[document_examples]
400 fn from(thunk: Thunk<'static, A>) -> Self {
408 Trampoline::new(move || thunk.evaluate())
409 }
410 }
411
412 impl_kind! {
413 for ThunkBrand {
414 type Of<'a, A: 'a>: 'a = Thunk<'a, A>;
415 }
416 }
417
418 #[document_type_parameters(
419 "The lifetime of the computation.",
420 "The type of the value produced by the computation."
421 )]
422 impl<'a, A: 'a> Deferrable<'a> for Thunk<'a, A> {
423 #[document_signature]
425 #[document_parameters("A thunk that produces the thunk.")]
427 #[document_returns("The deferred thunk.")]
429 #[document_examples]
431 fn defer(f: impl FnOnce() -> Self + 'a) -> Self
444 where
445 Self: Sized, {
446 Thunk::defer(f)
447 }
448 }
449
450 impl Functor for ThunkBrand {
451 #[document_signature]
453 #[document_type_parameters(
455 "The lifetime of the computation.",
456 "The type of the value inside the `Thunk`.",
457 "The type of the result of the transformation."
458 )]
459 #[document_parameters(
461 "The function to apply to the result of the computation.",
462 "The `Thunk` instance."
463 )]
464 #[document_returns("A new `Thunk` instance with the transformed result.")]
466 #[document_examples]
467 fn map<'a, A: 'a, B: 'a>(
479 func: impl Fn(A) -> B + 'a,
480 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
481 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
482 fa.map(func)
483 }
484 }
485
486 impl Pointed for ThunkBrand {
487 #[document_signature]
489 #[document_type_parameters(
491 "The lifetime of the computation.",
492 "The type of the value to wrap."
493 )]
494 #[document_parameters("The value to wrap.")]
496 #[document_returns("A new `Thunk` instance containing the value.")]
498 #[document_examples]
500 fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
512 Thunk::pure(a)
513 }
514 }
515
516 impl Lift for ThunkBrand {
517 #[document_signature]
519 #[document_type_parameters(
521 "The lifetime of the computation.",
522 "The type of the first value.",
523 "The type of the second value.",
524 "The type of the result."
525 )]
526 #[document_parameters(
528 "The binary function to apply.",
529 "The first `Thunk`.",
530 "The second `Thunk`."
531 )]
532 #[document_returns(
534 "A new `Thunk` instance containing the result of applying the function."
535 )]
536 #[document_examples]
537 fn lift2<'a, A, B, C>(
550 func: impl Fn(A, B) -> C + 'a,
551 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
552 fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
553 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
554 where
555 A: Clone + 'a,
556 B: Clone + 'a,
557 C: 'a, {
558 fa.bind(move |a| fb.map(move |b| func(a, b)))
559 }
560 }
561
562 impl ApplyFirst for ThunkBrand {}
563 impl ApplySecond for ThunkBrand {}
564
565 impl Semiapplicative for ThunkBrand {
566 #[document_signature]
568 #[document_type_parameters(
570 "The lifetime of the computation.",
571 "The brand of the cloneable function wrapper.",
572 "The type of the input.",
573 "The type of the result."
574 )]
575 #[document_parameters(
577 "The `Thunk` containing the function.",
578 "The `Thunk` containing the value."
579 )]
580 #[document_returns(
582 "A new `Thunk` instance containing the result of applying the function."
583 )]
584 #[document_examples]
585 fn apply<'a, FnBrand: 'a + CloneFn, A: 'a + Clone, B: 'a>(
598 ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneFn>::Of<'a, A, B>>),
599 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
600 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
601 ff.bind(move |f| {
602 fa.map(
603 #[expect(clippy::redundant_closure, reason = "Required for move semantics")]
604 move |a| f(a),
605 )
606 })
607 }
608 }
609
610 impl Semimonad for ThunkBrand {
611 #[document_signature]
613 #[document_type_parameters(
615 "The lifetime of the computation.",
616 "The type of the result of the first computation.",
617 "The type of the result of the new computation."
618 )]
619 #[document_parameters(
621 "The first `Thunk`.",
622 "The function to apply to the result of the computation."
623 )]
624 #[document_returns("A new `Thunk` instance representing the chained computation.")]
626 #[document_examples]
627 fn bind<'a, A: 'a, B: 'a>(
639 ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
640 func: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
641 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
642 ma.bind(func)
643 }
644 }
645
646 impl MonadRec for ThunkBrand {
647 #[document_signature]
654 #[document_type_parameters(
656 "The lifetime of the computation.",
657 "The type of the initial value and loop state.",
658 "The type of the result."
659 )]
660 #[document_parameters("The step function.", "The initial value.")]
662 #[document_returns("The result of the computation.")]
664 #[document_examples]
666 fn tail_rec_m<'a, A: 'a, B: 'a>(
689 f: impl Fn(
690 A,
691 )
692 -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ControlFlow<B, A>>)
693 + 'a,
694 a: A,
695 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
696 Thunk::new(move || {
697 let mut current = a;
698 loop {
699 match f(current).evaluate() {
700 ControlFlow::Continue(next) => current = next,
701 ControlFlow::Break(res) => break res,
702 }
703 }
704 })
705 }
706 }
707
708 impl Extract for ThunkBrand {
709 #[document_signature]
711 #[document_type_parameters(
713 "The lifetime of the computation.",
714 "The type of the value inside the thunk."
715 )]
716 #[document_parameters("The thunk to extract from.")]
718 #[document_returns("The result of running the thunk.")]
720 #[document_examples]
722 fn extract<'a, A: 'a>(
735 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
736 ) -> A {
737 fa.evaluate()
738 }
739 }
740
741 impl Extend for ThunkBrand {
742 #[document_signature]
748 #[document_type_parameters(
750 "The lifetime of the computation.",
751 "The type of the value inside the thunk.",
752 "The result type of the extension function."
753 )]
754 #[document_parameters(
756 "The function that consumes a whole thunk and produces a value.",
757 "The thunk to extend over."
758 )]
759 #[document_returns("A new thunk containing the deferred result of applying the function.")]
761 #[document_examples]
763 fn extend<'a, A: 'a + Clone, B: 'a>(
776 f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
777 wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
778 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
779 Thunk::new(move || f(wa))
780 }
781 }
782
783 impl Foldable for ThunkBrand {
784 #[document_signature]
786 #[document_type_parameters(
788 "The lifetime of the computation.",
789 "The brand of the cloneable function to use.",
790 "The type of the elements in the structure.",
791 "The type of the accumulator."
792 )]
793 #[document_parameters(
795 "The function to apply to each element and the accumulator.",
796 "The initial value of the accumulator.",
797 "The `Thunk` to fold."
798 )]
799 #[document_returns("The final accumulator value.")]
801 #[document_examples]
802 fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
814 func: impl Fn(A, B) -> B + 'a,
815 initial: B,
816 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
817 ) -> B
818 where
819 FnBrand: CloneFn + 'a, {
820 func(fa.evaluate(), initial)
821 }
822
823 #[document_signature]
825 #[document_type_parameters(
827 "The lifetime of the computation.",
828 "The brand of the cloneable function to use.",
829 "The type of the elements in the structure.",
830 "The type of the accumulator."
831 )]
832 #[document_parameters(
834 "The function to apply to the accumulator and each element.",
835 "The initial value of the accumulator.",
836 "The `Thunk` to fold."
837 )]
838 #[document_returns("The final accumulator value.")]
840 #[document_examples]
841 fn fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
853 func: impl Fn(B, A) -> B + 'a,
854 initial: B,
855 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
856 ) -> B
857 where
858 FnBrand: CloneFn + 'a, {
859 func(initial, fa.evaluate())
860 }
861
862 #[document_signature]
864 #[document_type_parameters(
866 "The lifetime of the computation.",
867 "The brand of the cloneable function to use.",
868 "The type of the elements in the structure.",
869 "The type of the monoid."
870 )]
871 #[document_parameters("The mapping function.", "The Thunk to fold.")]
873 #[document_returns("The monoid value.")]
875 #[document_examples]
877 fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
890 func: impl Fn(A) -> M + 'a,
891 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
892 ) -> M
893 where
894 M: Monoid + 'a,
895 FnBrand: CloneFn + 'a, {
896 func(fa.evaluate())
897 }
898 }
899
900 impl WithIndex for ThunkBrand {
901 type Index = ();
902 }
903
904 impl FunctorWithIndex for ThunkBrand {
905 #[document_signature]
907 #[document_type_parameters(
908 "The lifetime of the computation.",
909 "The type of the value inside the thunk.",
910 "The type of the result of applying the function."
911 )]
912 #[document_parameters(
913 "The function to apply to the value and its index.",
914 "The thunk to map over."
915 )]
916 #[document_returns("A new thunk containing the result of applying the function.")]
917 #[document_examples]
918 fn map_with_index<'a, A: 'a, B: 'a>(
930 f: impl Fn((), A) -> B + 'a,
931 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
932 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
933 fa.map(move |a| f((), a))
934 }
935 }
936
937 impl FoldableWithIndex for ThunkBrand {
938 #[document_signature]
940 #[document_type_parameters(
941 "The lifetime of the computation.",
942 "The brand of the cloneable function to use.",
943 "The type of the value inside the thunk.",
944 "The monoid type."
945 )]
946 #[document_parameters(
947 "The function to apply to the value and its index.",
948 "The thunk to fold."
949 )]
950 #[document_returns("The monoid value.")]
951 #[document_examples]
952 fn fold_map_with_index<'a, FnBrand, A: 'a + Clone, R: Monoid>(
967 f: impl Fn((), A) -> R + 'a,
968 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
969 ) -> R
970 where
971 FnBrand: LiftFn + 'a, {
972 f((), fa.evaluate())
973 }
974 }
975
976 #[document_type_parameters(
977 "The lifetime of the computation.",
978 "The type of the value produced by the computation."
979 )]
980 impl<'a, A: Semigroup + 'a> Semigroup for Thunk<'a, A> {
981 #[document_signature]
983 #[document_parameters("The first `Thunk`.", "The second `Thunk`.")]
985 #[document_returns("A new `Thunk` containing the combined result.")]
987 #[document_examples]
989 fn append(
1003 a: Self,
1004 b: Self,
1005 ) -> Self {
1006 Thunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
1007 }
1008 }
1009
1010 #[document_type_parameters(
1011 "The lifetime of the computation.",
1012 "The type of the value produced by the computation."
1013 )]
1014 impl<'a, A: Monoid + 'a> Monoid for Thunk<'a, A> {
1015 #[document_signature]
1017 #[document_returns("A `Thunk` producing the identity value of `A`.")]
1019 #[document_examples]
1021 fn empty() -> Self {
1032 Thunk::new(|| Monoid::empty())
1033 }
1034 }
1035
1036 #[document_type_parameters(
1037 "The lifetime of the computation.",
1038 "The type of the computed value."
1039 )]
1040 #[document_parameters("The thunk to format.")]
1041 impl<'a, A> fmt::Debug for Thunk<'a, A> {
1042 #[document_signature]
1044 #[document_parameters("The formatter.")]
1045 #[document_returns("The formatting result.")]
1046 #[document_examples]
1047 fn fmt(
1054 &self,
1055 f: &mut fmt::Formatter<'_>,
1056 ) -> fmt::Result {
1057 f.write_str("Thunk(<unevaluated>)")
1058 }
1059 }
1060}
1061pub use inner::*;
1062
1063#[cfg(test)]
1064mod tests {
1065 use {
1066 super::*,
1067 crate::{
1068 brands::*,
1069 functions::*,
1070 },
1071 quickcheck_macros::quickcheck,
1072 };
1073
1074 #[test]
1078 fn test_basic_execution() {
1079 let thunk = Thunk::new(|| 42);
1080 assert_eq!(thunk.evaluate(), 42);
1081 }
1082
1083 #[test]
1087 fn test_pure() {
1088 let thunk = Thunk::pure(42);
1089 assert_eq!(thunk.evaluate(), 42);
1090 }
1091
1092 #[test]
1096 fn test_borrowing() {
1097 let x = 42;
1098 let thunk = Thunk::new(|| &x);
1099 assert_eq!(thunk.evaluate(), &42);
1100 }
1101
1102 #[test]
1106 fn test_map() {
1107 let thunk = Thunk::pure(21).map(|x| x * 2);
1108 assert_eq!(thunk.evaluate(), 42);
1109 }
1110
1111 #[test]
1115 fn test_bind() {
1116 let thunk = Thunk::pure(21).bind(|x| Thunk::pure(x * 2));
1117 assert_eq!(thunk.evaluate(), 42);
1118 }
1119
1120 #[test]
1124 fn test_defer() {
1125 let thunk = Thunk::defer(|| Thunk::pure(42));
1126 assert_eq!(thunk.evaluate(), 42);
1127 }
1128
1129 #[test]
1131 fn test_thunk_from_memo() {
1132 use crate::types::RcLazy;
1133 let memo = RcLazy::new(|| 42);
1134 let thunk = Thunk::from(memo);
1135 assert_eq!(thunk.evaluate(), 42);
1136 }
1137
1138 #[test]
1143 fn test_thunk_from_send_thunk() {
1144 use crate::types::SendThunk;
1145 let send_thunk = SendThunk::new(|| 21 * 2);
1146 let thunk = Thunk::from(send_thunk);
1147 assert_eq!(thunk.evaluate(), 42);
1148 }
1149
1150 #[test]
1154 fn test_thunk_semigroup() {
1155 use crate::{
1156 brands::*,
1157 classes::semigroup::append,
1158 functions::*,
1159 };
1160 let t1 = pure::<ThunkBrand, _>("Hello".to_string());
1161 let t2 = pure::<ThunkBrand, _>(" World".to_string());
1162 let t3 = append(t1, t2);
1163 assert_eq!(t3.evaluate(), "Hello World");
1164 }
1165
1166 #[test]
1170 fn test_thunk_monoid() {
1171 use crate::classes::monoid::empty;
1172 let t: Thunk<String> = empty();
1173 assert_eq!(t.evaluate(), "");
1174 }
1175
1176 #[test]
1180 fn test_thunk_from_trampoline() {
1181 use crate::types::Trampoline;
1182
1183 let task = Trampoline::pure(42);
1184 let thunk = Thunk::from(task);
1185 assert_eq!(thunk.evaluate(), 42);
1186 }
1187
1188 #[test]
1192 fn test_thunk_from_trampoline_lazy() {
1193 use crate::types::Trampoline;
1194
1195 let task = Trampoline::new(|| 21 * 2);
1196 let thunk = Thunk::from(task);
1197 assert_eq!(thunk.evaluate(), 42);
1198 }
1199
1200 #[test]
1204 fn test_thunk_to_trampoline() {
1205 use crate::types::Trampoline;
1206 let thunk = Thunk::new(|| 42);
1207 let trampoline = Trampoline::from(thunk);
1208 assert_eq!(trampoline.evaluate(), 42);
1209 }
1210
1211 #[test]
1215 fn test_thunk_to_trampoline_chained() {
1216 use crate::types::Trampoline;
1217 let thunk = Thunk::pure(10).map(|x| x * 3).bind(|x| Thunk::pure(x + 12));
1218 let trampoline = Trampoline::from(thunk);
1219 assert_eq!(trampoline.evaluate(), 42);
1220 }
1221
1222 #[test]
1227 fn test_thunk_to_trampoline_non_send() {
1228 use {
1229 crate::types::Trampoline,
1230 std::rc::Rc,
1231 };
1232 let thunk = Thunk::new(|| Rc::new(42));
1233 let trampoline = Trampoline::from(thunk);
1234 assert_eq!(*trampoline.evaluate(), 42);
1235 }
1236
1237 #[quickcheck]
1243 fn functor_identity(x: i32) -> bool {
1244 explicit::map::<ThunkBrand, _, _, _, _>(identity, pure::<ThunkBrand, _>(x)).evaluate() == x
1245 }
1246
1247 #[quickcheck]
1249 fn functor_composition(x: i32) -> bool {
1250 let f = |a: i32| a.wrapping_add(1);
1251 let g = |a: i32| a.wrapping_mul(2);
1252 let lhs =
1253 explicit::map::<ThunkBrand, _, _, _, _>(move |a| f(g(a)), pure::<ThunkBrand, _>(x))
1254 .evaluate();
1255 let rhs = explicit::map::<ThunkBrand, _, _, _, _>(
1256 f,
1257 explicit::map::<ThunkBrand, _, _, _, _>(g, pure::<ThunkBrand, _>(x)),
1258 )
1259 .evaluate();
1260 lhs == rhs
1261 }
1262
1263 #[quickcheck]
1267 fn monad_left_identity(a: i32) -> bool {
1268 let f = |x: i32| pure::<ThunkBrand, _>(x.wrapping_mul(2));
1269 let lhs = explicit::bind::<ThunkBrand, _, _, _, _>(pure::<ThunkBrand, _>(a), f).evaluate();
1270 let rhs = f(a).evaluate();
1271 lhs == rhs
1272 }
1273
1274 #[quickcheck]
1276 fn monad_right_identity(x: i32) -> bool {
1277 let lhs = explicit::bind::<ThunkBrand, _, _, _, _>(
1278 pure::<ThunkBrand, _>(x),
1279 pure::<ThunkBrand, _>,
1280 )
1281 .evaluate();
1282 lhs == x
1283 }
1284
1285 #[quickcheck]
1287 fn monad_associativity(x: i32) -> bool {
1288 let f = |a: i32| pure::<ThunkBrand, _>(a.wrapping_add(1));
1289 let g = |a: i32| pure::<ThunkBrand, _>(a.wrapping_mul(3));
1290 let m = pure::<ThunkBrand, _>(x);
1291 let m2 = pure::<ThunkBrand, _>(x);
1292 let lhs = explicit::bind::<ThunkBrand, _, _, _, _>(
1293 explicit::bind::<ThunkBrand, _, _, _, _>(m, f),
1294 g,
1295 )
1296 .evaluate();
1297 let rhs = explicit::bind::<ThunkBrand, _, _, _, _>(m2, move |a| {
1298 explicit::bind::<ThunkBrand, _, _, _, _>(f(a), g)
1299 })
1300 .evaluate();
1301 lhs == rhs
1302 }
1303
1304 #[quickcheck]
1308 fn semigroup_associativity(
1309 a: String,
1310 b: String,
1311 c: String,
1312 ) -> bool {
1313 let ta = pure::<ThunkBrand, _>(a.clone());
1314 let tb = pure::<ThunkBrand, _>(b.clone());
1315 let tc = pure::<ThunkBrand, _>(c.clone());
1316 let ta2 = pure::<ThunkBrand, _>(a);
1317 let tb2 = pure::<ThunkBrand, _>(b);
1318 let tc2 = pure::<ThunkBrand, _>(c);
1319 let lhs = append(append(ta, tb), tc).evaluate();
1320 let rhs = append(ta2, append(tb2, tc2)).evaluate();
1321 lhs == rhs
1322 }
1323
1324 #[quickcheck]
1328 fn monoid_left_identity(x: String) -> bool {
1329 let a = pure::<ThunkBrand, _>(x.clone());
1330 let lhs: Thunk<String> = append(empty(), a);
1331 lhs.evaluate() == x
1332 }
1333
1334 #[quickcheck]
1336 fn monoid_right_identity(x: String) -> bool {
1337 let a = pure::<ThunkBrand, _>(x.clone());
1338 let rhs: Thunk<String> = append(a, empty());
1339 rhs.evaluate() == x
1340 }
1341
1342 #[test]
1346 fn test_foldable_via_brand() {
1347 let thunk = pure::<ThunkBrand, _>(10);
1348 let result =
1349 explicit::fold_right::<RcFnBrand, ThunkBrand, _, _, _, _>(|x, acc| x + acc, 5, thunk);
1350 assert_eq!(result, 15);
1351 }
1352
1353 #[test]
1355 fn test_lift2_via_brand() {
1356 let t1 = pure::<ThunkBrand, _>(10);
1357 let t2 = pure::<ThunkBrand, _>(20);
1358 let result = explicit::lift2::<ThunkBrand, _, _, _, _, _, _>(|a, b| a + b, t1, t2);
1359 assert_eq!(result.evaluate(), 30);
1360 }
1361
1362 #[test]
1364 fn test_apply_via_brand() {
1365 let func = pure::<ThunkBrand, _>(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
1366 let val = pure::<ThunkBrand, _>(21);
1367 let result = apply(func, val);
1368 assert_eq!(result.evaluate(), 42);
1369 }
1370
1371 #[test]
1373 fn test_extract_via_brand() {
1374 let thunk = pure::<ThunkBrand, _>(42);
1375 let result = extract::<ThunkBrand, _>(thunk);
1376 assert_eq!(result, 42);
1377 }
1378
1379 #[test]
1383 fn test_memoize_caching() {
1384 use std::cell::Cell;
1385
1386 let counter = Cell::new(0usize);
1387 let thunk = Thunk::new(|| {
1388 counter.set(counter.get() + 1);
1389 42
1390 });
1391 let lazy = thunk.into_rc_lazy();
1392
1393 assert_eq!(counter.get(), 0);
1394 assert_eq!(*lazy.evaluate(), 42);
1395 assert_eq!(counter.get(), 1);
1396 assert_eq!(*lazy.evaluate(), 42);
1397 assert_eq!(counter.get(), 1);
1398 }
1399
1400 #[test]
1402 fn test_memoize_arc_caching() {
1403 use std::sync::atomic::{
1404 AtomicUsize,
1405 Ordering,
1406 };
1407
1408 let counter = AtomicUsize::new(0);
1409 let thunk = Thunk::new(|| {
1410 counter.fetch_add(1, Ordering::SeqCst);
1411 42
1412 });
1413 let lazy = thunk.into_arc_lazy();
1414
1415 assert_eq!(counter.load(Ordering::SeqCst), 1);
1418 assert_eq!(*lazy.evaluate(), 42);
1419 assert_eq!(counter.load(Ordering::SeqCst), 1);
1420 assert_eq!(*lazy.evaluate(), 42);
1421 assert_eq!(counter.load(Ordering::SeqCst), 1);
1422 }
1423
1424 #[test]
1429 fn test_tail_rec_m_stack_safety() {
1430 use {
1431 crate::{
1432 brands::ThunkBrand,
1433 classes::monad_rec::tail_rec_m,
1434 functions::pure,
1435 },
1436 core::ops::ControlFlow,
1437 };
1438
1439 let iterations: i64 = 200_000;
1440 let result = tail_rec_m::<ThunkBrand, _, _>(
1441 |acc| {
1442 pure::<ThunkBrand, _>(
1443 if acc < iterations {
1444 ControlFlow::Continue(acc + 1)
1445 } else {
1446 ControlFlow::Break(acc)
1447 },
1448 )
1449 },
1450 0i64,
1451 );
1452 assert_eq!(result.evaluate(), iterations);
1453 }
1454
1455 #[test]
1459 fn test_functor_with_index() {
1460 use crate::{
1461 brands::ThunkBrand,
1462 classes::functor_with_index::FunctorWithIndex,
1463 functions::pure,
1464 };
1465
1466 let thunk = pure::<ThunkBrand, _>(21);
1467 let result = ThunkBrand::map_with_index(|(), x| x * 2, thunk);
1468 assert_eq!(result.evaluate(), 42);
1469 }
1470
1471 #[test]
1475 fn test_functor_with_index_identity() {
1476 use crate::{
1477 brands::ThunkBrand,
1478 classes::functor_with_index::FunctorWithIndex,
1479 functions::pure,
1480 };
1481
1482 let thunk = pure::<ThunkBrand, _>(42);
1483 let result = ThunkBrand::map_with_index(|_, a: i32| a, thunk);
1484 assert_eq!(result.evaluate(), 42);
1485 }
1486
1487 #[test]
1491 fn test_functor_with_index_compat_with_functor() {
1492 use crate::{
1493 brands::ThunkBrand,
1494 classes::functor_with_index::FunctorWithIndex,
1495 functions::{
1496 explicit,
1497 pure,
1498 },
1499 };
1500
1501 let f = |a: i32| a * 3 + 1;
1502 let thunk1 = pure::<ThunkBrand, _>(10);
1503 let thunk2 = pure::<ThunkBrand, _>(10);
1504 let via_map = explicit::map::<ThunkBrand, _, _, _, _>(f, thunk1).evaluate();
1505 let via_map_with_index = ThunkBrand::map_with_index(|_, a| f(a), thunk2).evaluate();
1506 assert_eq!(via_map, via_map_with_index);
1507 }
1508
1509 #[test]
1513 fn test_foldable_with_index() {
1514 use crate::{
1515 brands::ThunkBrand,
1516 classes::foldable_with_index::FoldableWithIndex,
1517 functions::pure,
1518 };
1519
1520 let thunk = pure::<ThunkBrand, _>(42);
1521 let result: String =
1522 ThunkBrand::fold_map_with_index::<RcFnBrand, _, _>(|(), a: i32| a.to_string(), thunk);
1523 assert_eq!(result, "42");
1524 }
1525
1526 #[test]
1530 fn test_foldable_with_index_compat_with_foldable() {
1531 use crate::{
1532 brands::*,
1533 classes::foldable_with_index::FoldableWithIndex,
1534 functions::{
1535 explicit,
1536 pure,
1537 },
1538 };
1539
1540 let f = |a: i32| a.to_string();
1541 let thunk1 = pure::<ThunkBrand, _>(99);
1542 let thunk2 = pure::<ThunkBrand, _>(99);
1543 let via_fold_map = explicit::fold_map::<RcFnBrand, ThunkBrand, _, _, _, _>(f, thunk1);
1544 let via_fold_map_with_index: String =
1545 ThunkBrand::fold_map_with_index::<RcFnBrand, _, _>(|_, a| f(a), thunk2);
1546 assert_eq!(via_fold_map, via_fold_map_with_index);
1547 }
1548
1549 #[quickcheck]
1553 fn extract_pure(x: i32) -> bool {
1554 extract::<ThunkBrand, _>(pure::<ThunkBrand, _>(x)) == x
1555 }
1556
1557 #[quickcheck]
1559 fn comonad_left_identity(x: i32) -> bool {
1560 use crate::classes::extend::extend;
1561 let f = |w: Thunk<i32>| w.evaluate().wrapping_mul(3);
1562 let wa = pure::<ThunkBrand, _>(x);
1563 let wa2 = pure::<ThunkBrand, _>(x);
1564 extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(f, wa)) == f(wa2)
1565 }
1566
1567 #[quickcheck]
1569 fn comonad_right_identity(x: i32) -> bool {
1570 use crate::classes::extend::extend;
1571 let wa = pure::<ThunkBrand, _>(x);
1572 extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(extract::<ThunkBrand, _>, wa)) == x
1573 }
1574
1575 #[quickcheck]
1578 fn extend_associativity(x: i32) -> bool {
1579 use crate::classes::extend::extend;
1580 let g = |w: Thunk<i32>| w.evaluate().wrapping_mul(2);
1581 let f = |w: Thunk<i32>| w.evaluate().wrapping_add(1);
1582 let lhs = extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(
1583 f,
1584 extend::<ThunkBrand, _, _>(g, pure::<ThunkBrand, _>(x)),
1585 ));
1586 let rhs = extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(
1587 |w: Thunk<i32>| f(extend::<ThunkBrand, _, _>(g, w)),
1588 pure::<ThunkBrand, _>(x),
1589 ));
1590 lhs == rhs
1591 }
1592
1593 #[test]
1595 fn extend_test() {
1596 use crate::classes::extend::extend;
1597 let thunk = Thunk::new(|| 21);
1598 let result = extend::<ThunkBrand, _, _>(|w: Thunk<i32>| w.evaluate() * 2, thunk);
1599 assert_eq!(result.evaluate(), 42);
1600 }
1601
1602 #[quickcheck]
1604 fn comonad_map_extract(x: i32) -> bool {
1605 let f = |a: i32| a.wrapping_mul(3).wrapping_add(7);
1606 let fa = Thunk::new(|| x);
1607 let fa2 = Thunk::new(|| x);
1608 let lhs = extract::<ThunkBrand, _>(explicit::map::<ThunkBrand, _, _, _, _>(f, fa));
1609 let rhs = f(extract::<ThunkBrand, _>(fa2));
1610 lhs == rhs
1611 }
1612}