Skip to main content

fp_library/types/
send_thunk.rs

1//! Thread-safe deferred, non-memoized computation.
2//!
3//! Like [`Thunk`](crate::types::Thunk) but with a `Send` bound on the inner closure,
4//! enabling thread-safe deferred computation chains and truly lazy
5//! [`into_arc_lazy`](SendThunk::into_arc_lazy) without eager evaluation.
6//!
7//! Standard HKT traits (`Functor`, `Semimonad`, etc.) cannot be implemented because
8//! their signatures do not require `Send` on mapping functions. Use the inherent
9//! methods ([`map`](SendThunk::map), [`bind`](SendThunk::bind)) instead.
10
11#[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	/// A thread-safe deferred computation that produces a value of type `A`.
42	///
43	/// `SendThunk` is the `Send`-capable counterpart of [`Thunk`]. It wraps a
44	/// `Box<dyn FnOnce() -> A + Send + 'a>`, so it can be transferred across thread
45	/// boundaries. Like `Thunk`, it is NOT memoized and does not cache results.
46	///
47	/// The key advantage over `Thunk` is that [`into_arc_lazy`](SendThunk::into_arc_lazy)
48	/// can wrap the closure lazily in an [`ArcLazy`] without forcing evaluation
49	/// first, because the inner closure satisfies `Send`.
50	///
51	/// ### Higher-Kinded Type Representation
52	///
53	/// The higher-kinded representation of this type constructor is
54	/// [`SendThunkBrand`](crate::brands::SendThunkBrand), which is fully
55	/// polymorphic over the result type.
56	///
57	/// ### Trade-offs vs Other Lazy Types
58	///
59	/// | Aspect         | `SendThunk<'a, A>`            | `Thunk<'a, A>`                | `Trampoline<A>`              | `ArcLazy<'a, A>`             |
60	/// |----------------|-------------------------------|-------------------------------|------------------------------|------------------------------|
61	/// | Thread safety  | `Send`                        | Not `Send`                    | Not `Send`                   | `Send + Sync`                |
62	/// | HKT compatible | No (needs `Send` closures)    | Yes                           | No (requires `'static`)      | Partial (`SendRefFunctor`)   |
63	/// | Stack-safe     | Partial (`tail_rec_m` only)   | Partial (`tail_rec_m` only)   | Yes (unlimited)              | N/A (memoized)               |
64	/// | Memoized       | No                            | No                            | No                           | Yes                          |
65	/// | Lifetime       | `'a` (can borrow)             | `'a` (can borrow)             | `'static` only               | `'a` (can borrow)            |
66	/// | Use case       | Cross-thread lazy pipelines   | Glue code, composition        | Deep recursion, pipelines    | Shared cached values          |
67	///
68	/// ### HKT Trait Limitations
69	///
70	/// Standard HKT traits such as `Functor`, `Pointed`, `Semimonad`, and
71	/// `Semiapplicative` cannot be implemented for `SendThunkBrand` because
72	/// their signatures do not require `Send` on the mapping or binding
73	/// functions. Since `SendThunk` stores a `Box<dyn FnOnce() -> A + Send>`,
74	/// composing it with a non-`Send` closure would violate the `Send` invariant.
75	///
76	/// Use the inherent methods ([`map`](SendThunk::map),
77	/// [`bind`](SendThunk::bind)) instead, which accept `Send` closures
78	/// explicitly.
79	///
80	/// ### Algebraic Properties
81	///
82	/// `SendThunk` satisfies the monad laws through its inherent methods, even though
83	/// it cannot implement the HKT `Monad` trait (due to the `Send` bound requirement):
84	/// - `pure(a).bind(f) ≡ f(a)` (left identity).
85	/// - `m.bind(|x| pure(x)) ≡ m` (right identity).
86	/// - `m.bind(f).bind(g) ≡ m.bind(|x| f(x).bind(g))` (associativity).
87	///
88	/// ### Stack Safety
89	///
90	/// Like `Thunk`, `SendThunk::bind` chains are **not** stack-safe. Each nested
91	/// [`bind`](SendThunk::bind) adds a frame to the call stack.
92	#[document_type_parameters(
93		"The lifetime of the computation.",
94		"The type of the value produced by the computation."
95	)]
96	///
97	pub struct SendThunk<'a, A>(
98		/// The thread-safe closure that performs the computation.
99		Box<dyn FnOnce() -> A + Send + 'a>,
100	);
101
102	// INVARIANT: SendThunk is Send because its inner closure is Send.
103	// The Box<dyn FnOnce() -> A + Send + 'a> already guarantees Send on the closure.
104	// Rust auto-derives Send for Box<dyn ... + Send>, so this is sound.
105
106	#[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		/// Returns the inner boxed closure, erasing the `Send` bound.
113		///
114		/// This is a crate-internal helper used by `From<SendThunk> for Thunk`
115		/// to perform a zero-cost unsizing coercion.
116		#[document_signature]
117		#[document_returns("The inner boxed closure with the `Send` bound erased.")]
118		#[document_examples]
119		///
120		/// ```
121		/// use fp_library::types::*;
122		///
123		/// let send_thunk = SendThunk::new(|| 42);
124		/// let thunk = Thunk::from(send_thunk);
125		/// assert_eq!(thunk.evaluate(), 42);
126		/// ```
127		#[inline]
128		pub(crate) fn into_inner(self) -> Box<dyn FnOnce() -> A + 'a> {
129			self.0
130		}
131
132		/// Creates a new `SendThunk` from a thread-safe closure.
133		#[document_signature]
134		///
135		#[document_parameters("The thread-safe closure to wrap.")]
136		///
137		#[document_returns("A new `SendThunk` instance.")]
138		///
139		#[document_examples]
140		///
141		/// ```
142		/// use fp_library::types::*;
143		///
144		/// let thunk = SendThunk::new(|| 42);
145		/// assert_eq!(thunk.evaluate(), 42);
146		/// ```
147		#[inline]
148		pub fn new(f: impl FnOnce() -> A + Send + 'a) -> Self {
149			SendThunk(Box::new(f))
150		}
151
152		/// Returns a pure value (already computed).
153		#[document_signature]
154		///
155		#[document_parameters("The value to wrap.")]
156		///
157		#[document_returns("A new `SendThunk` instance containing the value.")]
158		///
159		#[document_examples]
160		///
161		/// ```
162		/// use fp_library::types::*;
163		///
164		/// let thunk = SendThunk::pure(42);
165		/// assert_eq!(thunk.evaluate(), 42);
166		/// ```
167		#[inline]
168		pub fn pure(a: A) -> Self
169		where
170			A: Send + 'a, {
171			SendThunk::new(move || a)
172		}
173
174		/// Defers a computation that returns a `SendThunk`.
175		#[document_signature]
176		///
177		#[document_parameters("The thunk that returns a `SendThunk`.")]
178		///
179		#[document_returns("A new `SendThunk` instance.")]
180		///
181		#[document_examples]
182		///
183		/// ```
184		/// use fp_library::types::*;
185		///
186		/// let thunk = SendThunk::defer(|| SendThunk::pure(42));
187		/// assert_eq!(thunk.evaluate(), 42);
188		/// ```
189		#[inline]
190		pub fn defer(f: impl FnOnce() -> SendThunk<'a, A> + Send + 'a) -> Self {
191			SendThunk::new(move || f().evaluate())
192		}
193
194		/// Monadic bind: chains computations.
195		///
196		/// Note: Each `bind` adds to the call stack. For deep recursion,
197		/// consider converting to [`Trampoline`](crate::types::Trampoline).
198		#[document_signature]
199		///
200		#[document_type_parameters("The type of the result of the new computation.")]
201		///
202		#[document_parameters("The function to apply to the result of the computation.")]
203		///
204		#[document_returns("A new `SendThunk` instance representing the chained computation.")]
205		///
206		#[document_examples]
207		///
208		/// ```
209		/// use fp_library::types::*;
210		///
211		/// let thunk = SendThunk::pure(21).bind(|x| SendThunk::pure(x * 2));
212		/// assert_eq!(thunk.evaluate(), 42);
213		/// ```
214		#[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		/// Functor map: transforms the result.
227		#[document_signature]
228		///
229		#[document_type_parameters("The type of the result of the transformation.")]
230		///
231		#[document_parameters("The function to apply to the result of the computation.")]
232		///
233		#[document_returns("A new `SendThunk` instance with the transformed result.")]
234		///
235		#[document_examples]
236		///
237		/// ```
238		/// use fp_library::types::*;
239		///
240		/// let thunk = SendThunk::pure(21).map(|x| x * 2);
241		/// assert_eq!(thunk.evaluate(), 42);
242		/// ```
243		#[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		/// Forces evaluation and returns the result.
252		#[document_signature]
253		///
254		#[document_returns("The result of the computation.")]
255		///
256		#[document_examples]
257		///
258		/// ```
259		/// use fp_library::types::*;
260		///
261		/// let thunk = SendThunk::pure(42);
262		/// assert_eq!(thunk.evaluate(), 42);
263		/// ```
264		#[inline]
265		pub fn evaluate(self) -> A {
266			(self.0)()
267		}
268
269		/// Performs tail-recursive monadic computation.
270		///
271		/// The step function `f` is called in a loop, avoiding stack growth.
272		/// Each iteration evaluates `f(state)` and inspects the resulting
273		/// [`ControlFlow`]: `ControlFlow::Continue(next)` continues with `next`, while
274		/// `ControlFlow::Break(a)` breaks out and returns `a`.
275		///
276		/// # Clone Bound
277		///
278		/// The function `f` must implement `Clone` because each iteration
279		/// of the recursion may need its own copy. Most closures naturally
280		/// implement `Clone` when all their captures implement `Clone`.
281		///
282		/// For closures that do not implement `Clone`, use
283		/// [`arc_tail_rec_m`](SendThunk::arc_tail_rec_m), which wraps the
284		/// closure in `Arc` internally.
285		#[document_signature]
286		///
287		#[document_type_parameters("The type of the loop state.")]
288		///
289		#[document_parameters(
290			"The step function that produces the next state or the final result.",
291			"The initial state."
292		)]
293		///
294		#[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
295		///
296		#[document_examples]
297		///
298		/// ```
299		/// use {
300		/// 	core::ops::ControlFlow,
301		/// 	fp_library::types::*,
302		/// };
303		///
304		/// let result = SendThunk::tail_rec_m(
305		/// 	|x| {
306		/// 		SendThunk::pure(
307		/// 			if x < 1000 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
308		/// 		)
309		/// 	},
310		/// 	0,
311		/// );
312		/// assert_eq!(result.evaluate(), 1000);
313		/// ```
314		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		/// Arc-wrapped version of [`tail_rec_m`](SendThunk::tail_rec_m) for non-Clone closures.
332		///
333		/// Use this when your closure captures non-Clone state. The closure is
334		/// wrapped in [`Arc`] internally, which provides the required `Clone`
335		/// implementation.
336		#[document_signature]
337		///
338		#[document_type_parameters("The type of the loop state.")]
339		///
340		#[document_parameters(
341			"The step function that produces the next state or the final result.",
342			"The initial state."
343		)]
344		///
345		#[document_returns("A `SendThunk` that, when evaluated, runs the tail-recursive loop.")]
346		///
347		#[document_examples]
348		///
349		/// ```
350		/// use {
351		/// 	core::ops::ControlFlow,
352		/// 	fp_library::types::*,
353		/// 	std::sync::{
354		/// 		Arc,
355		/// 		atomic::{
356		/// 			AtomicUsize,
357		/// 			Ordering,
358		/// 		},
359		/// 	},
360		/// };
361		///
362		/// let counter = Arc::new(AtomicUsize::new(0));
363		/// let counter_clone = Arc::clone(&counter);
364		/// let result = SendThunk::arc_tail_rec_m(
365		/// 	move |x| {
366		/// 		counter_clone.fetch_add(1, Ordering::SeqCst);
367		/// 		SendThunk::pure(
368		/// 			if x < 100 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
369		/// 		)
370		/// 	},
371		/// 	0,
372		/// );
373		/// assert_eq!(result.evaluate(), 100);
374		/// assert_eq!(counter.load(Ordering::SeqCst), 101);
375		/// ```
376		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		/// Converts this `SendThunk` into a memoized [`ArcLazy`] value.
391		///
392		/// Unlike [`Thunk::into_arc_lazy`](crate::types::Thunk::into_arc_lazy), this
393		/// does **not** evaluate eagerly. The inner `Send` closure is passed
394		/// directly into `ArcLazy::new`, so evaluation is deferred until the
395		/// `ArcLazy` is first accessed.
396		#[document_signature]
397		///
398		#[document_returns("A thread-safe `ArcLazy` that evaluates this thunk on first access.")]
399		///
400		#[document_examples]
401		///
402		/// ```
403		/// use fp_library::types::*;
404		///
405		/// let thunk = SendThunk::new(|| 42);
406		/// let lazy = thunk.into_arc_lazy();
407		/// assert_eq!(*lazy.evaluate(), 42);
408		/// ```
409		#[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		/// Converts a [`Thunk`] into a [`SendThunk`].
421		///
422		/// The `Thunk` closure is not `Send`, so the conversion eagerly
423		/// evaluates it and wraps the owned result in a new `SendThunk`.
424		#[document_signature]
425		#[document_parameters("The thunk to convert.")]
426		#[document_returns("A new `SendThunk` wrapping the evaluated result.")]
427		#[document_examples]
428		///
429		/// ```
430		/// use fp_library::types::*;
431		/// let thunk = Thunk::pure(42);
432		/// let send_thunk = SendThunk::from(thunk);
433		/// assert_eq!(send_thunk.evaluate(), 42);
434		/// ```
435		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		/// Folds the `SendThunk` from the right.
448		#[document_signature]
449		///
450		#[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		///
457		#[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		///
463		#[document_returns("The final accumulator value.")]
464		#[document_examples]
465		///
466		/// ```
467		/// use fp_library::{
468		/// 	brands::*,
469		/// 	functions::*,
470		/// 	types::*,
471		/// };
472		///
473		/// let thunk = SendThunk::pure(10);
474		/// let result = fold_right::<RcFnBrand, SendThunkBrand, _, _>(|a, b| a + b, 5, thunk);
475		/// assert_eq!(result, 15);
476		/// ```
477		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		/// Folds the `SendThunk` from the left.
488		#[document_signature]
489		///
490		#[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		///
497		#[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		///
503		#[document_returns("The final accumulator value.")]
504		#[document_examples]
505		///
506		/// ```
507		/// use fp_library::{
508		/// 	brands::*,
509		/// 	functions::*,
510		/// 	types::*,
511		/// };
512		///
513		/// let thunk = SendThunk::pure(10);
514		/// let result = fold_left::<RcFnBrand, SendThunkBrand, _, _>(|b, a| b + a, 5, thunk);
515		/// assert_eq!(result, 15);
516		/// ```
517		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		/// Maps the value to a monoid and returns it.
528		#[document_signature]
529		///
530		#[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		///
537		#[document_parameters("The mapping function.", "The `SendThunk` to fold.")]
538		///
539		#[document_returns("The monoid value.")]
540		///
541		#[document_examples]
542		///
543		/// ```
544		/// use fp_library::{
545		/// 	brands::*,
546		/// 	functions::*,
547		/// 	types::*,
548		/// };
549		///
550		/// let thunk = SendThunk::pure(10);
551		/// let result = fold_map::<RcFnBrand, SendThunkBrand, _, _>(|a| a.to_string(), thunk);
552		/// assert_eq!(result, "10");
553		/// ```
554		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		/// Folds the send thunk using a monoid, providing the index `()`.
571		#[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		///
584		/// ```
585		/// use fp_library::{
586		/// 	brands::SendThunkBrand,
587		/// 	classes::foldable_with_index::FoldableWithIndex,
588		/// 	types::*,
589		/// };
590		///
591		/// let thunk = SendThunk::pure(5);
592		/// let result = <SendThunkBrand as FoldableWithIndex>::fold_map_with_index(
593		/// 	|_, x: i32| x.to_string(),
594		/// 	thunk,
595		/// );
596		/// assert_eq!(result, "5");
597		/// ```
598		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		/// Creates a `SendThunk` from a computation that produces it.
612		///
613		/// The thunk `f` is called eagerly because `Deferrable::defer` does not
614		/// require `Send` on the closure.
615		#[document_signature]
616		///
617		#[document_parameters("A thunk that produces the send thunk.")]
618		///
619		#[document_returns("The deferred send thunk.")]
620		///
621		#[document_examples]
622		///
623		/// ```
624		/// use fp_library::{
625		/// 	classes::Deferrable,
626		/// 	types::*,
627		/// };
628		///
629		/// let task: SendThunk<i32> = Deferrable::defer(|| SendThunk::pure(42));
630		/// assert_eq!(task.evaluate(), 42);
631		/// ```
632		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		/// Creates a `SendThunk` from a thread-safe computation that produces it.
645		#[document_signature]
646		///
647		#[document_parameters("A thread-safe thunk that produces the send thunk.")]
648		///
649		#[document_returns("The deferred send thunk.")]
650		///
651		#[document_examples]
652		///
653		/// ```
654		/// use fp_library::{
655		/// 	classes::SendDeferrable,
656		/// 	types::*,
657		/// };
658		///
659		/// let task: SendThunk<i32> = SendDeferrable::send_defer(|| SendThunk::pure(42));
660		/// assert_eq!(task.evaluate(), 42);
661		/// ```
662		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		/// Combines two `SendThunk`s by combining their results.
675		#[document_signature]
676		///
677		#[document_parameters("The first `SendThunk`.", "The second `SendThunk`.")]
678		///
679		#[document_returns("A new `SendThunk` containing the combined result.")]
680		///
681		#[document_examples]
682		///
683		/// ```
684		/// use fp_library::{
685		/// 	classes::*,
686		/// 	functions::*,
687		/// 	types::*,
688		/// };
689		///
690		/// let t1 = SendThunk::pure("Hello".to_string());
691		/// let t2 = SendThunk::pure(" World".to_string());
692		/// let t3 = append::<_>(t1, t2);
693		/// assert_eq!(t3.evaluate(), "Hello World");
694		/// ```
695		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		/// Returns the identity `SendThunk`.
709		#[document_signature]
710		///
711		#[document_returns("A `SendThunk` producing the identity value of `A`.")]
712		///
713		#[document_examples]
714		///
715		/// ```
716		/// use fp_library::{
717		/// 	classes::*,
718		/// 	types::*,
719		/// };
720		///
721		/// let t: SendThunk<String> = SendThunk::empty();
722		/// assert_eq!(t.evaluate(), "");
723		/// ```
724		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		/// Formats the send thunk without evaluating it.
736		#[document_signature]
737		#[document_parameters("The formatter.")]
738		#[document_returns("The formatting result.")]
739		#[document_examples]
740		///
741		/// ```
742		/// use fp_library::types::*;
743		/// let thunk = SendThunk::pure(42);
744		/// assert_eq!(format!("{:?}", thunk), "SendThunk(<unevaluated>)");
745		/// ```
746		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		// Second access returns cached value.
803		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	/// Tests that a `SendThunk` can be sent to another thread and evaluated there.
854	///
855	/// Verifies that `SendThunk` satisfies the `Send` bound by moving it across a
856	/// thread boundary via `std::thread::spawn`.
857	#[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	/// Tests that a mapped `SendThunk` evaluates correctly on another thread.
866	#[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	/// Tests that a bound `SendThunk` evaluates correctly on another thread.
875	#[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		// fold_map(f, fa) = fold_map_with_index(|_, a| f(a), fa)
1032		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 Law Tests
1039
1040	// Functor Laws
1041
1042	/// Functor identity: `send_thunk.map(identity).evaluate() == send_thunk.evaluate()`.
1043	#[quickcheck]
1044	fn functor_identity(x: i32) -> bool {
1045		SendThunk::pure(x).map(|a| a).evaluate() == x
1046	}
1047
1048	/// Functor composition: `send_thunk.map(f).map(g) == send_thunk.map(|x| g(f(x)))`.
1049	#[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	// Monad Laws
1059
1060	/// Monad left identity: `SendThunk::pure(a).bind(f) == f(a)`.
1061	#[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	/// Monad right identity: `send_thunk.bind(SendThunk::pure) == send_thunk`.
1070	#[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	/// Monad associativity: `m.bind(f).bind(g) == m.bind(|x| f(x).bind(g))`.
1077	#[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}