1#[fp_macros::document_module]
6mod inner {
7 use {
8 crate::{
9 Apply,
10 brands::ThunkBrand,
11 classes::{
12 ApplyFirst,
13 ApplySecond,
14 CloneableFn,
15 Deferrable,
16 Evaluable,
17 Foldable,
18 Functor,
19 Lift,
20 MonadRec,
21 Monoid,
22 Pointed,
23 Semiapplicative,
24 Semigroup,
25 Semimonad,
26 },
27 impl_kind,
28 kinds::*,
29 types::{
30 Lazy,
31 LazyConfig,
32 Step,
33 },
34 },
35 fp_macros::*,
36 };
37
38 #[document_type_parameters(
79 "The lifetime of the computation.",
80 "The type of the value produced by the computation."
81 )]
82 #[document_fields("The closure that performs the computation.")]
84 pub struct Thunk<'a, A>(Box<dyn FnOnce() -> A + 'a>);
86
87 #[document_type_parameters(
88 "The lifetime of the computation.",
89 "The type of the value produced by the computation."
90 )]
91 #[document_parameters("The thunk instance.")]
92 impl<'a, A: 'a> Thunk<'a, A> {
93 #[document_signature]
95 #[document_parameters("The thunk to wrap.")]
97 #[document_returns("A new `Thunk` instance.")]
99 #[document_examples]
101 pub fn new(f: impl FnOnce() -> A + 'a) -> Self {
109 Thunk(Box::new(f))
110 }
111
112 #[document_signature]
114 #[document_parameters("The value to wrap.")]
116 #[document_returns("A new `Thunk` instance containing the value.")]
118 #[document_examples]
120 pub fn pure(a: A) -> Self
132 where
133 A: 'a, {
134 Thunk::new(move || a)
135 }
136
137 #[document_signature]
139 #[document_parameters("The thunk that returns a `Thunk`.")]
141 #[document_returns("A new `Thunk` instance.")]
143 #[document_examples]
145 pub fn defer(f: impl FnOnce() -> Thunk<'a, A> + 'a) -> Self {
157 Thunk::new(move || f().evaluate())
158 }
159
160 #[document_signature]
165 #[document_type_parameters("The type of the result of the new computation.")]
167 #[document_parameters("The function to apply to the result of the computation.")]
169 #[document_returns("A new `Thunk` instance representing the chained computation.")]
171 #[document_examples]
173 pub fn bind<B: 'a>(
184 self,
185 f: impl FnOnce(A) -> Thunk<'a, B> + 'a,
186 ) -> Thunk<'a, B> {
187 Thunk::new(move || {
188 let a = (self.0)();
189 let thunk_b = f(a);
190 (thunk_b.0)()
191 })
192 }
193
194 #[document_signature]
196 #[document_type_parameters("The type of the result of the transformation.")]
198 #[document_parameters("The function to apply to the result of the computation.")]
200 #[document_returns("A new `Thunk` instance with the transformed result.")]
202 #[document_examples]
204 pub fn map<B: 'a>(
215 self,
216 f: impl FnOnce(A) -> B + 'a,
217 ) -> Thunk<'a, B> {
218 Thunk::new(move || f((self.0)()))
219 }
220
221 #[document_signature]
223 #[document_returns("The result of the computation.")]
225 #[document_examples]
227 pub fn evaluate(self) -> A {
238 (self.0)()
239 }
240 }
241
242 #[document_type_parameters(
243 "The lifetime of the computation.",
244 "The type of the value produced by the computation.",
245 "The memoization configuration."
246 )]
247 impl<'a, A, Config> From<Lazy<'a, A, Config>> for Thunk<'a, A>
248 where
249 A: Clone + 'a,
250 Config: LazyConfig,
251 {
252 #[document_signature]
253 #[document_parameters("The lazy value to convert.")]
254 #[document_returns("A thunk that evaluates the lazy value.")]
255 #[document_examples]
256 fn from(lazy: Lazy<'a, A, Config>) -> Self {
264 Thunk::new(move || lazy.evaluate().clone())
265 }
266 }
267
268 impl_kind! {
269 for ThunkBrand {
270 type Of<'a, A: 'a>: 'a = Thunk<'a, A>;
271 }
272 }
273
274 #[document_type_parameters(
275 "The lifetime of the computation.",
276 "The type of the value produced by the computation."
277 )]
278 impl<'a, A: 'a> Deferrable<'a> for Thunk<'a, A> {
279 #[document_signature]
281 #[document_parameters("A thunk that produces the thunk.")]
283 #[document_returns("The deferred thunk.")]
285 #[document_examples]
287 fn defer(f: impl FnOnce() -> Self + 'a) -> Self
300 where
301 Self: Sized, {
302 Thunk::defer(f)
303 }
304 }
305
306 impl Functor for ThunkBrand {
307 #[document_signature]
309 #[document_type_parameters(
311 "The lifetime of the computation.",
312 "The type of the value inside the `Thunk`.",
313 "The type of the result of the transformation."
314 )]
315 #[document_parameters(
317 "The function to apply to the result of the computation.",
318 "The `Thunk` instance."
319 )]
320 #[document_returns("A new `Thunk` instance with the transformed result.")]
322 #[document_examples]
323 fn map<'a, A: 'a, B: 'a>(
335 func: impl Fn(A) -> B + 'a,
336 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
337 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
338 fa.map(func)
339 }
340 }
341
342 impl Pointed for ThunkBrand {
343 #[document_signature]
345 #[document_type_parameters(
347 "The lifetime of the computation.",
348 "The type of the value to wrap."
349 )]
350 #[document_parameters("The value to wrap.")]
352 #[document_returns("A new `Thunk` instance containing the value.")]
354 #[document_examples]
356 fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
368 Thunk::pure(a)
369 }
370 }
371
372 impl Lift for ThunkBrand {
373 #[document_signature]
375 #[document_type_parameters(
377 "The lifetime of the computation.",
378 "The type of the first value.",
379 "The type of the second value.",
380 "The type of the result."
381 )]
382 #[document_parameters(
384 "The binary function to apply.",
385 "The first `Thunk`.",
386 "The second `Thunk`."
387 )]
388 #[document_returns(
390 "A new `Thunk` instance containing the result of applying the function."
391 )]
392 #[document_examples]
393 fn lift2<'a, A, B, C>(
406 func: impl Fn(A, B) -> C + 'a,
407 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
408 fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
409 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
410 where
411 A: Clone + 'a,
412 B: Clone + 'a,
413 C: 'a, {
414 fa.bind(move |a| fb.map(move |b| func(a, b)))
415 }
416 }
417
418 impl ApplyFirst for ThunkBrand {}
419 impl ApplySecond for ThunkBrand {}
420
421 impl Semiapplicative for ThunkBrand {
422 #[document_signature]
424 #[document_type_parameters(
426 "The lifetime of the computation.",
427 "The brand of the cloneable function wrapper.",
428 "The type of the input.",
429 "The type of the result."
430 )]
431 #[document_parameters(
433 "The `Thunk` containing the function.",
434 "The `Thunk` containing the value."
435 )]
436 #[document_returns(
438 "A new `Thunk` instance containing the result of applying the function."
439 )]
440 #[document_examples]
441 fn apply<'a, FnBrand: 'a + CloneableFn, A: 'a + Clone, B: 'a>(
454 ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneableFn>::Of<'a, A, B>>),
455 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
456 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
457 ff.bind(move |f| {
458 fa.map(
459 #[allow(clippy::redundant_closure)] move |a| f(a),
461 )
462 })
463 }
464 }
465
466 impl Semimonad for ThunkBrand {
467 #[document_signature]
469 #[document_type_parameters(
471 "The lifetime of the computation.",
472 "The type of the result of the first computation.",
473 "The type of the result of the new computation."
474 )]
475 #[document_parameters(
477 "The first `Thunk`.",
478 "The function to apply to the result of the computation."
479 )]
480 #[document_returns("A new `Thunk` instance representing the chained computation.")]
482 #[document_examples]
483 fn bind<'a, A: 'a, B: 'a>(
495 ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
496 func: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
497 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
498 ma.bind(func)
499 }
500 }
501
502 impl MonadRec for ThunkBrand {
503 #[document_signature]
505 #[document_type_parameters(
507 "The lifetime of the computation.",
508 "The type of the initial value and loop state.",
509 "The type of the result."
510 )]
511 #[document_parameters("The step function.", "The initial value.")]
513 #[document_returns("The result of the computation.")]
515 #[document_examples]
517 fn tail_rec_m<'a, A: 'a, B: 'a>(
533 f: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Step<A, B>>)
534 + Clone
535 + 'a,
536 a: A,
537 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
538 Thunk::new(move || {
539 let mut current = a;
540 loop {
541 match f(current).evaluate() {
542 Step::Loop(next) => current = next,
543 Step::Done(res) => break res,
544 }
545 }
546 })
547 }
548 }
549
550 impl Evaluable for ThunkBrand {
551 #[document_signature]
553 #[document_type_parameters(
555 "The lifetime of the computation.",
556 "The type of the value inside the thunk."
557 )]
558 #[document_parameters("The eval to run.")]
560 #[document_returns("The result of running the thunk.")]
562 #[document_examples]
564 fn evaluate<'a, A: 'a>(
577 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
578 ) -> A {
579 fa.evaluate()
580 }
581 }
582
583 impl Foldable for ThunkBrand {
584 #[document_signature]
586 #[document_type_parameters(
588 "The lifetime of the computation.",
589 "The brand of the cloneable function to use.",
590 "The type of the elements in the structure.",
591 "The type of the accumulator."
592 )]
593 #[document_parameters(
595 "The function to apply to each element and the accumulator.",
596 "The initial value of the accumulator.",
597 "The `Thunk` to fold."
598 )]
599 #[document_returns("The final accumulator value.")]
601 #[document_examples]
602 fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
614 func: impl Fn(A, B) -> B + 'a,
615 initial: B,
616 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
617 ) -> B
618 where
619 FnBrand: CloneableFn + 'a, {
620 func(fa.evaluate(), initial)
621 }
622
623 #[document_signature]
625 #[document_type_parameters(
627 "The lifetime of the computation.",
628 "The brand of the cloneable function to use.",
629 "The type of the elements in the structure.",
630 "The type of the accumulator."
631 )]
632 #[document_parameters(
634 "The function to apply to the accumulator and each element.",
635 "The initial value of the accumulator.",
636 "The `Thunk` to fold."
637 )]
638 #[document_returns("The final accumulator value.")]
640 #[document_examples]
641 fn fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
653 func: impl Fn(B, A) -> B + 'a,
654 initial: B,
655 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
656 ) -> B
657 where
658 FnBrand: CloneableFn + 'a, {
659 func(initial, fa.evaluate())
660 }
661
662 #[document_signature]
664 #[document_type_parameters(
666 "The lifetime of the computation.",
667 "The brand of the cloneable function to use.",
668 "The type of the elements in the structure.",
669 "The type of the monoid."
670 )]
671 #[document_parameters("The mapping function.", "The Thunk to fold.")]
673 #[document_returns("The monoid value.")]
675 #[document_examples]
677 fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
689 func: impl Fn(A) -> M + 'a,
690 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
691 ) -> M
692 where
693 M: Monoid + 'a,
694 FnBrand: CloneableFn + 'a, {
695 func(fa.evaluate())
696 }
697 }
698
699 #[document_type_parameters(
700 "The lifetime of the computation.",
701 "The type of the value produced by the computation."
702 )]
703 impl<'a, A: Semigroup + 'a> Semigroup for Thunk<'a, A> {
704 #[document_signature]
706 #[document_parameters("The first `Thunk`.", "The second `Thunk`.")]
708 #[document_returns("A new `Thunk` containing the combined result.")]
710 #[document_examples]
712 fn append(
726 a: Self,
727 b: Self,
728 ) -> Self {
729 Thunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
730 }
731 }
732
733 #[document_type_parameters(
734 "The lifetime of the computation.",
735 "The type of the value produced by the computation."
736 )]
737 impl<'a, A: Monoid + 'a> Monoid for Thunk<'a, A> {
738 #[document_signature]
740 #[document_returns("A `Thunk` producing the identity value of `A`.")]
742 #[document_examples]
744 fn empty() -> Self {
755 Thunk::new(|| Monoid::empty())
756 }
757 }
758}
759pub use inner::*;
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
769 fn test_basic_execution() {
770 let thunk = Thunk::new(|| 42);
771 assert_eq!(thunk.evaluate(), 42);
772 }
773
774 #[test]
778 fn test_pure() {
779 let thunk = Thunk::pure(42);
780 assert_eq!(thunk.evaluate(), 42);
781 }
782
783 #[test]
787 fn test_borrowing() {
788 let x = 42;
789 let thunk = Thunk::new(|| &x);
790 assert_eq!(thunk.evaluate(), &42);
791 }
792
793 #[test]
797 fn test_map() {
798 let thunk = Thunk::pure(21).map(|x| x * 2);
799 assert_eq!(thunk.evaluate(), 42);
800 }
801
802 #[test]
806 fn test_bind() {
807 let thunk = Thunk::pure(21).bind(|x| Thunk::pure(x * 2));
808 assert_eq!(thunk.evaluate(), 42);
809 }
810
811 #[test]
815 fn test_defer() {
816 let thunk = Thunk::defer(|| Thunk::pure(42));
817 assert_eq!(thunk.evaluate(), 42);
818 }
819
820 #[test]
822 fn test_eval_from_memo() {
823 use crate::types::RcLazy;
824 let memo = RcLazy::new(|| 42);
825 let thunk = Thunk::from(memo);
826 assert_eq!(thunk.evaluate(), 42);
827 }
828
829 #[test]
833 fn test_eval_semigroup() {
834 use crate::{
835 brands::*,
836 classes::semigroup::append,
837 functions::*,
838 };
839 let t1 = pure::<ThunkBrand, _>("Hello".to_string());
840 let t2 = pure::<ThunkBrand, _>(" World".to_string());
841 let t3 = append(t1, t2);
842 assert_eq!(t3.evaluate(), "Hello World");
843 }
844
845 #[test]
849 fn test_eval_monoid() {
850 use crate::classes::monoid::empty;
851 let t: Thunk<String> = empty();
852 assert_eq!(t.evaluate(), "");
853 }
854}