1#[fp_macros::document_module]
12mod inner {
13 use {
14 crate::{
15 brands::SendThunkBrand,
16 classes::{
17 CloneableFn,
18 Deferrable,
19 Foldable,
20 FoldableWithIndex,
21 Monoid,
22 Semigroup,
23 SendDeferrable,
24 WithIndex,
25 },
26 impl_kind,
27 kinds::*,
28 types::{
29 ArcLazy,
30 Thunk,
31 },
32 },
33 core::ops::ControlFlow,
34 fp_macros::*,
35 std::{
36 fmt,
37 sync::Arc,
38 },
39 };
40
41 #[document_type_parameters(
93 "The lifetime of the computation.",
94 "The type of the value produced by the computation."
95 )]
96 pub struct SendThunk<'a, A>(
98 Box<dyn FnOnce() -> A + Send + 'a>,
100 );
101
102 #[document_type_parameters(
107 "The lifetime of the computation.",
108 "The type of the value produced by the computation."
109 )]
110 #[document_parameters("The send thunk instance.")]
111 impl<'a, A: 'a> SendThunk<'a, A> {
112 #[document_signature]
117 #[document_returns("The inner boxed closure with the `Send` bound erased.")]
118 #[document_examples]
119 #[inline]
128 pub(crate) fn into_inner(self) -> Box<dyn FnOnce() -> A + 'a> {
129 self.0
130 }
131
132 #[document_signature]
134 #[document_parameters("The thread-safe closure to wrap.")]
136 #[document_returns("A new `SendThunk` instance.")]
138 #[document_examples]
140 #[inline]
148 pub fn new(f: impl FnOnce() -> A + Send + 'a) -> Self {
149 SendThunk(Box::new(f))
150 }
151
152 #[document_signature]
154 #[document_parameters("The value to wrap.")]
156 #[document_returns("A new `SendThunk` instance containing the value.")]
158 #[document_examples]
160 #[inline]
168 pub fn pure(a: A) -> Self
169 where
170 A: Send + 'a, {
171 SendThunk::new(move || a)
172 }
173
174 #[document_signature]
176 #[document_parameters("The thunk that returns a `SendThunk`.")]
178 #[document_returns("A new `SendThunk` instance.")]
180 #[document_examples]
182 #[inline]
190 pub fn defer(f: impl FnOnce() -> SendThunk<'a, A> + Send + 'a) -> Self {
191 SendThunk::new(move || f().evaluate())
192 }
193
194 #[document_signature]
199 #[document_type_parameters("The type of the result of the new computation.")]
201 #[document_parameters("The function to apply to the result of the computation.")]
203 #[document_returns("A new `SendThunk` instance representing the chained computation.")]
205 #[document_examples]
207 #[inline]
215 pub fn bind<B: 'a>(
216 self,
217 f: impl FnOnce(A) -> SendThunk<'a, B> + Send + 'a,
218 ) -> SendThunk<'a, B> {
219 SendThunk::new(move || {
220 let a = (self.0)();
221 let thunk_b = f(a);
222 (thunk_b.0)()
223 })
224 }
225
226 #[document_signature]
228 #[document_type_parameters("The type of the result of the transformation.")]
230 #[document_parameters("The function to apply to the result of the computation.")]
232 #[document_returns("A new `SendThunk` instance with the transformed result.")]
234 #[document_examples]
236 #[inline]
244 pub fn map<B: 'a>(
245 self,
246 f: impl FnOnce(A) -> B + Send + 'a,
247 ) -> SendThunk<'a, B> {
248 SendThunk::new(move || f((self.0)()))
249 }
250
251 #[document_signature]
253 #[document_returns("The result of the computation.")]
255 #[document_examples]
257 #[inline]
265 pub fn evaluate(self) -> A {
266 (self.0)()
267 }
268
269 #[document_signature]
286 #[document_type_parameters("The type of the loop state.")]
288 #[document_parameters(
290 "The step function that produces the next state or the final result.",
291 "The initial state."
292 )]
293 #[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
295 #[document_examples]
297 pub fn tail_rec_m<S>(
315 f: impl Fn(S) -> SendThunk<'a, ControlFlow<A, S>> + Send + 'a,
316 initial: S,
317 ) -> Self
318 where
319 S: Send + 'a, {
320 SendThunk::new(move || {
321 let mut state = initial;
322 loop {
323 match f(state).evaluate() {
324 ControlFlow::Break(a) => return a,
325 ControlFlow::Continue(next) => state = next,
326 }
327 }
328 })
329 }
330
331 #[document_signature]
337 #[document_type_parameters("The type of the loop state.")]
339 #[document_parameters(
341 "The step function that produces the next state or the final result.",
342 "The initial state."
343 )]
344 #[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
346 #[document_examples]
348 pub fn arc_tail_rec_m<S>(
377 f: impl Fn(S) -> SendThunk<'a, ControlFlow<A, S>> + Send + Sync + 'a,
378 initial: S,
379 ) -> Self
380 where
381 S: Send + 'a, {
382 let f = Arc::new(f);
383 let wrapper = move |s: S| {
384 let f = Arc::clone(&f);
385 f(s)
386 };
387 Self::tail_rec_m(wrapper, initial)
388 }
389
390 #[document_signature]
397 #[document_returns("A thread-safe `ArcLazy` that evaluates this thunk on first access.")]
399 #[document_examples]
401 #[inline]
410 pub fn into_arc_lazy(self) -> ArcLazy<'a, A> {
411 self.into()
412 }
413 }
414
415 #[document_type_parameters("The lifetime of the computation.", "The type of the value.")]
416 impl<'a, A: 'a> From<Thunk<'a, A>> for SendThunk<'a, A>
417 where
418 A: Send,
419 {
420 #[document_signature]
425 #[document_parameters("The thunk to convert.")]
426 #[document_returns("A new `SendThunk` wrapping the evaluated result.")]
427 #[document_examples]
428 fn from(thunk: Thunk<'a, A>) -> Self {
436 SendThunk::pure(thunk.evaluate())
437 }
438 }
439
440 impl_kind! {
441 for SendThunkBrand {
442 type Of<'a, A: 'a>: 'a = SendThunk<'a, A>;
443 }
444 }
445
446 impl Foldable for SendThunkBrand {
447 #[document_signature]
449 #[document_type_parameters(
451 "The lifetime of the computation.",
452 "The brand of the cloneable function to use.",
453 "The type of the elements in the structure.",
454 "The type of the accumulator."
455 )]
456 #[document_parameters(
458 "The function to apply to each element and the accumulator.",
459 "The initial value of the accumulator.",
460 "The `SendThunk` to fold."
461 )]
462 #[document_returns("The final accumulator value.")]
464 #[document_examples]
465 fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
478 func: impl Fn(A, B) -> B + 'a,
479 initial: B,
480 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
481 ) -> B
482 where
483 FnBrand: CloneableFn + 'a, {
484 func(fa.evaluate(), initial)
485 }
486
487 #[document_signature]
489 #[document_type_parameters(
491 "The lifetime of the computation.",
492 "The brand of the cloneable function to use.",
493 "The type of the elements in the structure.",
494 "The type of the accumulator."
495 )]
496 #[document_parameters(
498 "The function to apply to the accumulator and each element.",
499 "The initial value of the accumulator.",
500 "The `SendThunk` to fold."
501 )]
502 #[document_returns("The final accumulator value.")]
504 #[document_examples]
505 fn fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
518 func: impl Fn(B, A) -> B + 'a,
519 initial: B,
520 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
521 ) -> B
522 where
523 FnBrand: CloneableFn + 'a, {
524 func(initial, fa.evaluate())
525 }
526
527 #[document_signature]
529 #[document_type_parameters(
531 "The lifetime of the computation.",
532 "The brand of the cloneable function to use.",
533 "The type of the elements in the structure.",
534 "The type of the monoid."
535 )]
536 #[document_parameters("The mapping function.", "The `SendThunk` to fold.")]
538 #[document_returns("The monoid value.")]
540 #[document_examples]
542 fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
555 func: impl Fn(A) -> M + 'a,
556 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
557 ) -> M
558 where
559 M: Monoid + 'a,
560 FnBrand: CloneableFn + 'a, {
561 func(fa.evaluate())
562 }
563 }
564
565 impl WithIndex for SendThunkBrand {
566 type Index = ();
567 }
568
569 impl FoldableWithIndex for SendThunkBrand {
570 #[document_signature]
572 #[document_type_parameters(
573 "The lifetime of the computation.",
574 "The type of the value inside the send thunk.",
575 "The monoid type."
576 )]
577 #[document_parameters(
578 "The function to apply to the value and its index.",
579 "The send thunk to fold."
580 )]
581 #[document_returns("The monoid value.")]
582 #[document_examples]
583 fn fold_map_with_index<'a, A: 'a + Clone, R: Monoid>(
599 f: impl Fn((), A) -> R + 'a,
600 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
601 ) -> R {
602 f((), fa.evaluate())
603 }
604 }
605
606 #[document_type_parameters(
607 "The lifetime of the computation.",
608 "The type of the value produced by the computation."
609 )]
610 impl<'a, A: 'a> Deferrable<'a> for SendThunk<'a, A> {
611 #[document_signature]
616 #[document_parameters("A thunk that produces the send thunk.")]
618 #[document_returns("The deferred send thunk.")]
620 #[document_examples]
622 fn defer(f: impl FnOnce() -> Self + 'a) -> Self
633 where
634 Self: Sized, {
635 f()
636 }
637 }
638
639 #[document_type_parameters(
640 "The lifetime of the computation.",
641 "The type of the value produced by the computation."
642 )]
643 impl<'a, A: Send + 'a> SendDeferrable<'a> for SendThunk<'a, A> {
644 #[document_signature]
646 #[document_parameters("A thread-safe thunk that produces the send thunk.")]
648 #[document_returns("The deferred send thunk.")]
650 #[document_examples]
652 fn send_defer(f: impl FnOnce() -> Self + Send + 'a) -> Self
663 where
664 Self: Sized, {
665 SendThunk::defer(f)
666 }
667 }
668
669 #[document_type_parameters(
670 "The lifetime of the computation.",
671 "The type of the value produced by the computation."
672 )]
673 impl<'a, A: Semigroup + Send + 'a> Semigroup for SendThunk<'a, A> {
674 #[document_signature]
676 #[document_parameters("The first `SendThunk`.", "The second `SendThunk`.")]
678 #[document_returns("A new `SendThunk` containing the combined result.")]
680 #[document_examples]
682 fn append(
696 a: Self,
697 b: Self,
698 ) -> Self {
699 SendThunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
700 }
701 }
702
703 #[document_type_parameters(
704 "The lifetime of the computation.",
705 "The type of the value produced by the computation."
706 )]
707 impl<'a, A: Monoid + Send + 'a> Monoid for SendThunk<'a, A> {
708 #[document_signature]
710 #[document_returns("A `SendThunk` producing the identity value of `A`.")]
712 #[document_examples]
714 fn empty() -> Self {
725 SendThunk::new(|| Monoid::empty())
726 }
727 }
728
729 #[document_type_parameters(
730 "The lifetime of the computation.",
731 "The type of the computed value."
732 )]
733 #[document_parameters("The send thunk to format.")]
734 impl<'a, A> fmt::Debug for SendThunk<'a, A> {
735 #[document_signature]
737 #[document_parameters("The formatter.")]
738 #[document_returns("The formatting result.")]
739 #[document_examples]
740 fn fmt(
747 &self,
748 f: &mut fmt::Formatter<'_>,
749 ) -> fmt::Result {
750 f.write_str("SendThunk(<unevaluated>)")
751 }
752 }
753}
754pub use inner::*;
755
756#[cfg(test)]
757mod tests {
758 use {
759 super::*,
760 crate::classes::{
761 monoid::empty,
762 semigroup::append,
763 },
764 quickcheck_macros::quickcheck,
765 };
766
767 #[test]
768 fn test_send_thunk_pure_and_evaluate() {
769 let thunk = SendThunk::pure(42);
770 assert_eq!(thunk.evaluate(), 42);
771 }
772
773 #[test]
774 fn test_send_thunk_new() {
775 let thunk = SendThunk::new(|| 1 + 2);
776 assert_eq!(thunk.evaluate(), 3);
777 }
778
779 #[test]
780 fn test_send_thunk_map() {
781 let thunk = SendThunk::pure(10).map(|x| x * 3);
782 assert_eq!(thunk.evaluate(), 30);
783 }
784
785 #[test]
786 fn test_send_thunk_bind() {
787 let thunk = SendThunk::pure(5).bind(|x| SendThunk::pure(x + 10));
788 assert_eq!(thunk.evaluate(), 15);
789 }
790
791 #[test]
792 fn test_send_thunk_defer() {
793 let thunk = SendThunk::defer(|| SendThunk::pure(99));
794 assert_eq!(thunk.evaluate(), 99);
795 }
796
797 #[test]
798 fn test_send_thunk_into_arc_lazy() {
799 let thunk = SendThunk::new(|| 42);
800 let lazy = thunk.into_arc_lazy();
801 assert_eq!(*lazy.evaluate(), 42);
802 assert_eq!(*lazy.evaluate(), 42);
804 }
805
806 #[test]
807 fn test_send_thunk_semigroup() {
808 let t1 = SendThunk::pure("Hello".to_string());
809 let t2 = SendThunk::pure(" World".to_string());
810 let t3 = append(t1, t2);
811 assert_eq!(t3.evaluate(), "Hello World");
812 }
813
814 #[test]
815 fn test_send_thunk_monoid() {
816 let t: SendThunk<String> = empty();
817 assert_eq!(t.evaluate(), "");
818 }
819
820 #[test]
821 fn test_send_thunk_from_thunk() {
822 let thunk = crate::types::Thunk::pure(42);
823 let send_thunk = SendThunk::from(thunk);
824 assert_eq!(send_thunk.evaluate(), 42);
825 }
826
827 #[test]
828 fn test_send_thunk_debug() {
829 let thunk = SendThunk::pure(42);
830 assert_eq!(format!("{:?}", thunk), "SendThunk(<unevaluated>)");
831 }
832
833 #[test]
834 fn test_send_thunk_is_send() {
835 fn assert_send<T: Send>() {}
836 assert_send::<SendThunk<'static, i32>>();
837 }
838
839 #[test]
840 fn test_send_thunk_deferrable() {
841 use crate::classes::Deferrable;
842 let task: SendThunk<i32> = Deferrable::defer(|| SendThunk::pure(42));
843 assert_eq!(task.evaluate(), 42);
844 }
845
846 #[test]
847 fn test_send_thunk_send_deferrable() {
848 use crate::classes::SendDeferrable;
849 let task: SendThunk<i32> = SendDeferrable::send_defer(|| SendThunk::pure(42));
850 assert_eq!(task.evaluate(), 42);
851 }
852
853 #[test]
858 fn test_send_thunk_cross_thread() {
859 let thunk = SendThunk::new(|| 42 * 2);
860 let handle = std::thread::spawn(move || thunk.evaluate());
861 let result = handle.join().expect("thread should not panic");
862 assert_eq!(result, 84);
863 }
864
865 #[test]
867 fn test_send_thunk_cross_thread_with_map() {
868 let thunk = SendThunk::pure(10).map(|x| x + 5).map(|x| x * 3);
869 let handle = std::thread::spawn(move || thunk.evaluate());
870 let result = handle.join().expect("thread should not panic");
871 assert_eq!(result, 45);
872 }
873
874 #[test]
876 fn test_send_thunk_cross_thread_with_bind() {
877 let thunk = SendThunk::pure(7).bind(|x| SendThunk::pure(x * 6));
878 let handle = std::thread::spawn(move || thunk.evaluate());
879 let result = handle.join().expect("thread should not panic");
880 assert_eq!(result, 42);
881 }
882
883 #[test]
884 fn test_send_thunk_tail_rec_m() {
885 use core::ops::ControlFlow;
886 let result = SendThunk::tail_rec_m(
887 |x| {
888 SendThunk::pure(
889 if x < 1000 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
890 )
891 },
892 0,
893 );
894 assert_eq!(result.evaluate(), 1000);
895 }
896
897 #[test]
898 fn test_send_thunk_tail_rec_m_stack_safety() {
899 use core::ops::ControlFlow;
900 let iterations: i64 = 200_000;
901 let result = SendThunk::tail_rec_m(
902 |acc| {
903 SendThunk::pure(
904 if acc < iterations {
905 ControlFlow::Continue(acc + 1)
906 } else {
907 ControlFlow::Break(acc)
908 },
909 )
910 },
911 0i64,
912 );
913 assert_eq!(result.evaluate(), iterations);
914 }
915
916 #[test]
917 fn test_send_thunk_arc_tail_rec_m() {
918 use {
919 core::ops::ControlFlow,
920 std::sync::{
921 Arc,
922 atomic::{
923 AtomicUsize,
924 Ordering,
925 },
926 },
927 };
928 let counter = Arc::new(AtomicUsize::new(0));
929 let counter_clone = Arc::clone(&counter);
930 let result = SendThunk::arc_tail_rec_m(
931 move |x| {
932 counter_clone.fetch_add(1, Ordering::SeqCst);
933 SendThunk::pure(
934 if x < 100 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
935 )
936 },
937 0,
938 );
939 assert_eq!(result.evaluate(), 100);
940 assert_eq!(counter.load(Ordering::SeqCst), 101);
941 }
942
943 #[test]
944 fn test_send_thunk_fold_right() {
945 use crate::{
946 brands::{
947 RcFnBrand,
948 SendThunkBrand,
949 },
950 classes::foldable::fold_right,
951 };
952 let thunk = SendThunk::pure(10);
953 let result = fold_right::<RcFnBrand, SendThunkBrand, _, _>(|a, b| a + b, 5, thunk);
954 assert_eq!(result, 15);
955 }
956
957 #[test]
958 fn test_send_thunk_fold_left() {
959 use crate::{
960 brands::{
961 RcFnBrand,
962 SendThunkBrand,
963 },
964 classes::foldable::fold_left,
965 };
966 let thunk = SendThunk::pure(10);
967 let result = fold_left::<RcFnBrand, SendThunkBrand, _, _>(|b, a| b + a, 5, thunk);
968 assert_eq!(result, 15);
969 }
970
971 #[test]
972 fn test_send_thunk_fold_map() {
973 use crate::{
974 brands::{
975 RcFnBrand,
976 SendThunkBrand,
977 },
978 classes::foldable::fold_map,
979 };
980 let thunk = SendThunk::pure(10);
981 let result = fold_map::<RcFnBrand, SendThunkBrand, _, _>(|a: i32| a.to_string(), thunk);
982 assert_eq!(result, "10");
983 }
984
985 #[test]
986 fn test_send_thunk_fold_map_with_index() {
987 use crate::{
988 brands::SendThunkBrand,
989 classes::foldable_with_index::FoldableWithIndex,
990 };
991 let thunk = SendThunk::pure(5);
992 let result = <SendThunkBrand as FoldableWithIndex>::fold_map_with_index(
993 |_, x: i32| x.to_string(),
994 thunk,
995 );
996 assert_eq!(result, "5");
997 }
998
999 #[test]
1000 fn test_send_thunk_foldable_with_index_receives_unit_index() {
1001 use crate::{
1002 brands::SendThunkBrand,
1003 classes::foldable_with_index::FoldableWithIndex,
1004 };
1005 let thunk = SendThunk::pure(42);
1006 let result = <SendThunkBrand as FoldableWithIndex>::fold_map_with_index(
1007 |idx, x: i32| {
1008 assert_eq!(idx, ());
1009 vec![x]
1010 },
1011 thunk,
1012 );
1013 assert_eq!(result, vec![42]);
1014 }
1015
1016 #[test]
1017 fn test_send_thunk_foldable_consistency() {
1018 use crate::{
1019 brands::{
1020 RcFnBrand,
1021 SendThunkBrand,
1022 },
1023 classes::{
1024 foldable::fold_map,
1025 foldable_with_index::FoldableWithIndex,
1026 },
1027 };
1028 let f = |a: i32| a.to_string();
1029 let t1 = SendThunk::pure(7);
1030 let t2 = SendThunk::pure(7);
1031 assert_eq!(
1033 fold_map::<RcFnBrand, SendThunkBrand, _, _>(f, t1),
1034 <SendThunkBrand as FoldableWithIndex>::fold_map_with_index(|_, a| f(a), t2),
1035 );
1036 }
1037
1038 #[quickcheck]
1044 fn functor_identity(x: i32) -> bool {
1045 SendThunk::pure(x).map(|a| a).evaluate() == x
1046 }
1047
1048 #[quickcheck]
1050 fn functor_composition(x: i32) -> bool {
1051 let f = |a: i32| a.wrapping_add(1);
1052 let g = |a: i32| a.wrapping_mul(2);
1053 let lhs = SendThunk::pure(x).map(f).map(g).evaluate();
1054 let rhs = SendThunk::pure(x).map(move |a| g(f(a))).evaluate();
1055 lhs == rhs
1056 }
1057
1058 #[quickcheck]
1062 fn monad_left_identity(a: i32) -> bool {
1063 let f = |x: i32| SendThunk::pure(x.wrapping_mul(2));
1064 let lhs = SendThunk::pure(a).bind(f).evaluate();
1065 let rhs = f(a).evaluate();
1066 lhs == rhs
1067 }
1068
1069 #[quickcheck]
1071 fn monad_right_identity(x: i32) -> bool {
1072 let lhs = SendThunk::pure(x).bind(SendThunk::pure).evaluate();
1073 lhs == x
1074 }
1075
1076 #[quickcheck]
1078 fn monad_associativity(x: i32) -> bool {
1079 let f = |a: i32| SendThunk::pure(a.wrapping_add(1));
1080 let g = |a: i32| SendThunk::pure(a.wrapping_mul(3));
1081 let lhs = SendThunk::pure(x).bind(f).bind(g).evaluate();
1082 let rhs = SendThunk::pure(x).bind(move |a| f(a).bind(g)).evaluate();
1083 lhs == rhs
1084 }
1085}