1#[fp_macros::document_module]
12mod inner {
13 use {
14 crate::{
15 brands::SendThunkBrand,
16 classes::{
17 CloneFn,
18 Foldable,
19 FoldableWithIndex,
20 LiftFn,
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]
282 #[document_type_parameters("The type of the loop state.")]
284 #[document_parameters(
286 "The step function that produces the next state or the final result.",
287 "The initial state."
288 )]
289 #[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
291 #[document_examples]
293 pub fn tail_rec_m<S>(
311 f: impl Fn(S) -> SendThunk<'a, ControlFlow<A, S>> + Send + 'a,
312 initial: S,
313 ) -> Self
314 where
315 S: Send + 'a, {
316 SendThunk::new(move || {
317 let mut state = initial;
318 loop {
319 match f(state).evaluate() {
320 ControlFlow::Break(a) => return a,
321 ControlFlow::Continue(next) => state = next,
322 }
323 }
324 })
325 }
326
327 #[document_signature]
333 #[document_type_parameters("The type of the loop state.")]
335 #[document_parameters(
337 "The step function that produces the next state or the final result.",
338 "The initial state."
339 )]
340 #[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
342 #[document_examples]
344 pub fn arc_tail_rec_m<S>(
373 f: impl Fn(S) -> SendThunk<'a, ControlFlow<A, S>> + Send + Sync + 'a,
374 initial: S,
375 ) -> Self
376 where
377 S: Send + 'a, {
378 let f = Arc::new(f);
379 let wrapper = move |s: S| {
380 let f = Arc::clone(&f);
381 f(s)
382 };
383 Self::tail_rec_m(wrapper, initial)
384 }
385
386 #[document_signature]
393 #[document_returns("A thread-safe `ArcLazy` that evaluates this thunk on first access.")]
395 #[document_examples]
397 #[inline]
406 pub fn into_arc_lazy(self) -> ArcLazy<'a, A>
407 where
408 A: Send + Sync, {
409 self.into()
410 }
411 }
412
413 #[document_type_parameters("The lifetime of the computation.", "The type of the value.")]
414 impl<'a, A: 'a> From<Thunk<'a, A>> for SendThunk<'a, A>
415 where
416 A: Send,
417 {
418 #[document_signature]
423 #[document_parameters("The thunk to convert.")]
424 #[document_returns("A new `SendThunk` wrapping the evaluated result.")]
425 #[document_examples]
426 fn from(thunk: Thunk<'a, A>) -> Self {
434 SendThunk::pure(thunk.evaluate())
435 }
436 }
437
438 impl_kind! {
439 for SendThunkBrand {
440 type Of<'a, A: 'a>: 'a = SendThunk<'a, A>;
441 }
442 }
443
444 impl Foldable for SendThunkBrand {
445 #[document_signature]
447 #[document_type_parameters(
449 "The lifetime of the computation.",
450 "The brand of the cloneable function to use.",
451 "The type of the elements in the structure.",
452 "The type of the accumulator."
453 )]
454 #[document_parameters(
456 "The function to apply to each element and the accumulator.",
457 "The initial value of the accumulator.",
458 "The `SendThunk` to fold."
459 )]
460 #[document_returns("The final accumulator value.")]
462 #[document_examples]
463 fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
477 func: impl Fn(A, B) -> B + 'a,
478 initial: B,
479 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
480 ) -> B
481 where
482 FnBrand: CloneFn + 'a, {
483 func(fa.evaluate(), initial)
484 }
485
486 #[document_signature]
488 #[document_type_parameters(
490 "The lifetime of the computation.",
491 "The brand of the cloneable function to use.",
492 "The type of the elements in the structure.",
493 "The type of the accumulator."
494 )]
495 #[document_parameters(
497 "The function to apply to the accumulator and each element.",
498 "The initial value of the accumulator.",
499 "The `SendThunk` to fold."
500 )]
501 #[document_returns("The final accumulator value.")]
503 #[document_examples]
504 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: CloneFn + '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>(
556 func: impl Fn(A) -> M + 'a,
557 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
558 ) -> M
559 where
560 M: Monoid + 'a,
561 FnBrand: CloneFn + 'a, {
562 func(fa.evaluate())
563 }
564 }
565
566 impl WithIndex for SendThunkBrand {
567 type Index = ();
568 }
569
570 impl FoldableWithIndex for SendThunkBrand {
571 #[document_signature]
573 #[document_type_parameters(
574 "The lifetime of the computation.",
575 "The brand of the cloneable function to use.",
576 "The type of the value inside the send thunk.",
577 "The monoid type."
578 )]
579 #[document_parameters(
580 "The function to apply to the value and its index.",
581 "The send thunk to fold."
582 )]
583 #[document_returns("The monoid value.")]
584 #[document_examples]
585 fn fold_map_with_index<'a, FnBrand, A: 'a + Clone, R: Monoid>(
601 f: impl Fn((), A) -> R + 'a,
602 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
603 ) -> R
604 where
605 FnBrand: LiftFn + 'a, {
606 f((), fa.evaluate())
607 }
608 }
609
610 #[document_type_parameters(
611 "The lifetime of the computation.",
612 "The type of the value produced by the computation."
613 )]
614 impl<'a, A: Send + 'a> SendDeferrable<'a> for SendThunk<'a, A> {
615 #[document_signature]
617 #[document_parameters("A thread-safe thunk that produces the send thunk.")]
619 #[document_returns("The deferred send thunk.")]
621 #[document_examples]
623 fn send_defer(f: impl FnOnce() -> Self + Send + 'a) -> Self
634 where
635 Self: Sized, {
636 SendThunk::defer(f)
637 }
638 }
639
640 #[document_type_parameters(
641 "The lifetime of the computation.",
642 "The type of the value produced by the computation."
643 )]
644 impl<'a, A: Semigroup + Send + 'a> Semigroup for SendThunk<'a, A> {
645 #[document_signature]
647 #[document_parameters("The first `SendThunk`.", "The second `SendThunk`.")]
649 #[document_returns("A new `SendThunk` containing the combined result.")]
651 #[document_examples]
653 fn append(
667 a: Self,
668 b: Self,
669 ) -> Self {
670 SendThunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
671 }
672 }
673
674 #[document_type_parameters(
675 "The lifetime of the computation.",
676 "The type of the value produced by the computation."
677 )]
678 impl<'a, A: Monoid + Send + 'a> Monoid for SendThunk<'a, A> {
679 #[document_signature]
681 #[document_returns("A `SendThunk` producing the identity value of `A`.")]
683 #[document_examples]
685 fn empty() -> Self {
696 SendThunk::new(|| Monoid::empty())
697 }
698 }
699
700 #[document_type_parameters(
701 "The lifetime of the computation.",
702 "The type of the computed value."
703 )]
704 #[document_parameters("The send thunk to format.")]
705 impl<'a, A> fmt::Debug for SendThunk<'a, A> {
706 #[document_signature]
708 #[document_parameters("The formatter.")]
709 #[document_returns("The formatting result.")]
710 #[document_examples]
711 fn fmt(
718 &self,
719 f: &mut fmt::Formatter<'_>,
720 ) -> fmt::Result {
721 f.write_str("SendThunk(<unevaluated>)")
722 }
723 }
724}
725pub use inner::*;
726
727#[cfg(test)]
728#[expect(clippy::expect_used, reason = "Tests use panicking operations for brevity and clarity")]
729mod tests {
730 use {
731 super::*,
732 crate::classes::{
733 monoid::empty,
734 semigroup::append,
735 },
736 quickcheck_macros::quickcheck,
737 };
738
739 #[test]
740 fn test_send_thunk_pure_and_evaluate() {
741 let thunk = SendThunk::pure(42);
742 assert_eq!(thunk.evaluate(), 42);
743 }
744
745 #[test]
746 fn test_send_thunk_new() {
747 let thunk = SendThunk::new(|| 1 + 2);
748 assert_eq!(thunk.evaluate(), 3);
749 }
750
751 #[test]
752 fn test_send_thunk_map() {
753 let thunk = SendThunk::pure(10).map(|x| x * 3);
754 assert_eq!(thunk.evaluate(), 30);
755 }
756
757 #[test]
758 fn test_send_thunk_bind() {
759 let thunk = SendThunk::pure(5).bind(|x| SendThunk::pure(x + 10));
760 assert_eq!(thunk.evaluate(), 15);
761 }
762
763 #[test]
764 fn test_send_thunk_defer() {
765 let thunk = SendThunk::defer(|| SendThunk::pure(99));
766 assert_eq!(thunk.evaluate(), 99);
767 }
768
769 #[test]
770 fn test_send_thunk_into_arc_lazy() {
771 let thunk = SendThunk::new(|| 42);
772 let lazy = thunk.into_arc_lazy();
773 assert_eq!(*lazy.evaluate(), 42);
774 assert_eq!(*lazy.evaluate(), 42);
776 }
777
778 #[test]
779 fn test_send_thunk_semigroup() {
780 let t1 = SendThunk::pure("Hello".to_string());
781 let t2 = SendThunk::pure(" World".to_string());
782 let t3 = append(t1, t2);
783 assert_eq!(t3.evaluate(), "Hello World");
784 }
785
786 #[test]
787 fn test_send_thunk_monoid() {
788 let t: SendThunk<String> = empty();
789 assert_eq!(t.evaluate(), "");
790 }
791
792 #[test]
793 fn test_send_thunk_from_thunk() {
794 let thunk = crate::types::Thunk::pure(42);
795 let send_thunk = SendThunk::from(thunk);
796 assert_eq!(send_thunk.evaluate(), 42);
797 }
798
799 #[test]
800 fn test_send_thunk_debug() {
801 let thunk = SendThunk::pure(42);
802 assert_eq!(format!("{:?}", thunk), "SendThunk(<unevaluated>)");
803 }
804
805 #[test]
806 fn test_send_thunk_is_send() {
807 fn assert_send<T: Send>() {}
808 assert_send::<SendThunk<'static, i32>>();
809 }
810
811 #[test]
812 fn test_send_thunk_send_deferrable() {
813 use crate::classes::SendDeferrable;
814 let task: SendThunk<i32> = SendDeferrable::send_defer(|| SendThunk::pure(42));
815 assert_eq!(task.evaluate(), 42);
816 }
817
818 #[test]
823 fn test_send_thunk_cross_thread() {
824 let thunk = SendThunk::new(|| 42 * 2);
825 let handle = std::thread::spawn(move || thunk.evaluate());
826 let result = handle.join().expect("thread should not panic");
827 assert_eq!(result, 84);
828 }
829
830 #[test]
832 fn test_send_thunk_cross_thread_with_map() {
833 let thunk = SendThunk::pure(10).map(|x| x + 5).map(|x| x * 3);
834 let handle = std::thread::spawn(move || thunk.evaluate());
835 let result = handle.join().expect("thread should not panic");
836 assert_eq!(result, 45);
837 }
838
839 #[test]
841 fn test_send_thunk_cross_thread_with_bind() {
842 let thunk = SendThunk::pure(7).bind(|x| SendThunk::pure(x * 6));
843 let handle = std::thread::spawn(move || thunk.evaluate());
844 let result = handle.join().expect("thread should not panic");
845 assert_eq!(result, 42);
846 }
847
848 #[test]
849 fn test_send_thunk_tail_rec_m() {
850 use core::ops::ControlFlow;
851 let result = SendThunk::tail_rec_m(
852 |x| {
853 SendThunk::pure(
854 if x < 1000 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
855 )
856 },
857 0,
858 );
859 assert_eq!(result.evaluate(), 1000);
860 }
861
862 #[test]
863 fn test_send_thunk_tail_rec_m_stack_safety() {
864 use core::ops::ControlFlow;
865 let iterations: i64 = 200_000;
866 let result = SendThunk::tail_rec_m(
867 |acc| {
868 SendThunk::pure(
869 if acc < iterations {
870 ControlFlow::Continue(acc + 1)
871 } else {
872 ControlFlow::Break(acc)
873 },
874 )
875 },
876 0i64,
877 );
878 assert_eq!(result.evaluate(), iterations);
879 }
880
881 #[test]
882 fn test_send_thunk_arc_tail_rec_m() {
883 use {
884 core::ops::ControlFlow,
885 std::sync::{
886 Arc,
887 atomic::{
888 AtomicUsize,
889 Ordering,
890 },
891 },
892 };
893 let counter = Arc::new(AtomicUsize::new(0));
894 let counter_clone = Arc::clone(&counter);
895 let result = SendThunk::arc_tail_rec_m(
896 move |x| {
897 counter_clone.fetch_add(1, Ordering::SeqCst);
898 SendThunk::pure(
899 if x < 100 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
900 )
901 },
902 0,
903 );
904 assert_eq!(result.evaluate(), 100);
905 assert_eq!(counter.load(Ordering::SeqCst), 101);
906 }
907
908 #[test]
909 fn test_send_thunk_fold_right() {
910 use crate::{
911 brands::{
912 RcFnBrand,
913 SendThunkBrand,
914 },
915 functions::explicit,
916 };
917 let thunk = SendThunk::pure(10);
918 let result =
919 explicit::fold_right::<RcFnBrand, SendThunkBrand, _, _, _, _>(|a, b| a + b, 5, thunk);
920 assert_eq!(result, 15);
921 }
922
923 #[test]
924 fn test_send_thunk_fold_left() {
925 use crate::{
926 brands::{
927 RcFnBrand,
928 SendThunkBrand,
929 },
930 functions::explicit,
931 };
932 let thunk = SendThunk::pure(10);
933 let result =
934 explicit::fold_left::<RcFnBrand, SendThunkBrand, _, _, _, _>(|b, a| b + a, 5, thunk);
935 assert_eq!(result, 15);
936 }
937
938 #[test]
939 fn test_send_thunk_fold_map() {
940 use crate::{
941 brands::{
942 RcFnBrand,
943 SendThunkBrand,
944 },
945 functions::explicit,
946 };
947 let thunk = SendThunk::pure(10);
948 let result = explicit::fold_map::<RcFnBrand, SendThunkBrand, _, _, _, _>(
949 |a: i32| a.to_string(),
950 thunk,
951 );
952 assert_eq!(result, "10");
953 }
954
955 #[test]
956 fn test_send_thunk_fold_map_with_index() {
957 use crate::{
958 brands::{
959 RcFnBrand,
960 SendThunkBrand,
961 },
962 classes::foldable_with_index::FoldableWithIndex,
963 };
964 let thunk = SendThunk::pure(5);
965 let result = <SendThunkBrand as FoldableWithIndex>::fold_map_with_index::<RcFnBrand, _, _>(
966 |_, x: i32| x.to_string(),
967 thunk,
968 );
969 assert_eq!(result, "5");
970 }
971
972 #[test]
973 fn test_send_thunk_foldable_with_index_receives_unit_index() {
974 use crate::{
975 brands::{
976 RcFnBrand,
977 SendThunkBrand,
978 },
979 classes::foldable_with_index::FoldableWithIndex,
980 };
981 let thunk = SendThunk::pure(42);
982 let result = <SendThunkBrand as FoldableWithIndex>::fold_map_with_index::<RcFnBrand, _, _>(
983 |idx, x: i32| {
984 assert_eq!(idx, ());
985 vec![x]
986 },
987 thunk,
988 );
989 assert_eq!(result, vec![42]);
990 }
991
992 #[test]
993 fn test_send_thunk_foldable_consistency() {
994 use crate::{
995 brands::{
996 RcFnBrand,
997 SendThunkBrand,
998 },
999 classes::foldable_with_index::FoldableWithIndex,
1000 functions::explicit,
1001 };
1002 let f = |a: i32| a.to_string();
1003 let t1 = SendThunk::pure(7);
1004 let t2 = SendThunk::pure(7);
1005 assert_eq!(
1007 explicit::fold_map::<RcFnBrand, SendThunkBrand, _, _, _, _>(f, t1),
1008 <SendThunkBrand as FoldableWithIndex>::fold_map_with_index::<RcFnBrand, _, _>(
1009 |_, a| f(a),
1010 t2
1011 ),
1012 );
1013 }
1014
1015 #[quickcheck]
1021 fn functor_identity(x: i32) -> bool {
1022 SendThunk::pure(x).map(|a| a).evaluate() == x
1023 }
1024
1025 #[quickcheck]
1027 fn functor_composition(x: i32) -> bool {
1028 let f = |a: i32| a.wrapping_add(1);
1029 let g = |a: i32| a.wrapping_mul(2);
1030 let lhs = SendThunk::pure(x).map(f).map(g).evaluate();
1031 let rhs = SendThunk::pure(x).map(move |a| g(f(a))).evaluate();
1032 lhs == rhs
1033 }
1034
1035 #[quickcheck]
1039 fn monad_left_identity(a: i32) -> bool {
1040 let f = |x: i32| SendThunk::pure(x.wrapping_mul(2));
1041 let lhs = SendThunk::pure(a).bind(f).evaluate();
1042 let rhs = f(a).evaluate();
1043 lhs == rhs
1044 }
1045
1046 #[quickcheck]
1048 fn monad_right_identity(x: i32) -> bool {
1049 let lhs = SendThunk::pure(x).bind(SendThunk::pure).evaluate();
1050 lhs == x
1051 }
1052
1053 #[quickcheck]
1055 fn monad_associativity(x: i32) -> bool {
1056 let f = |a: i32| SendThunk::pure(a.wrapping_add(1));
1057 let g = |a: i32| SendThunk::pure(a.wrapping_mul(3));
1058 let lhs = SendThunk::pure(x).bind(f).bind(g).evaluate();
1059 let rhs = SendThunk::pure(x).bind(move |a| f(a).bind(g)).evaluate();
1060 lhs == rhs
1061 }
1062}