1#[fp_macros::document_module]
67mod inner {
68 use {
69 crate::{
70 brands::CoyonedaExplicitBrand,
71 classes::*,
72 functions::{
73 compose,
74 identity,
75 },
76 impl_kind,
77 kinds::*,
78 types::Coyoneda,
79 },
80 fp_macros::*,
81 std::marker::PhantomData,
82 };
83
84 #[document_type_parameters(
101 "The lifetime of the values.",
102 "The brand of the underlying type constructor.",
103 "The type of the values in the underlying functor (the input to the accumulated function).",
104 "The current output type (the output of the accumulated function).",
105 "The type of the accumulated function from `B` to `A`."
106 )]
107 pub struct CoyonedaExplicit<
108 'a,
109 F,
110 B: 'a,
111 A: 'a,
112 Func: Fn(B) -> A + 'a = Box<dyn Fn(B) -> A + 'a>,
113 >
114 where
115 F: Kind_cdc7cd43dac7585f + 'a, {
116 fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
117 func: Func,
118 _phantom: PhantomData<A>,
119 }
120
121 pub type BoxedCoyonedaExplicit<'a, F, B, A> =
124 CoyonedaExplicit<'a, F, B, A, Box<dyn Fn(B) -> A + 'a>>;
125
126 #[document_type_parameters(
127 "The lifetime of the values.",
128 "The brand of the underlying type constructor.",
129 "The type of the values in the underlying functor.",
130 "The current output type.",
131 "The type of the accumulated function."
132 )]
133 #[document_parameters("The `CoyonedaExplicit` instance.")]
134 impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaExplicit<'a, F, B, A, Func>
135 where
136 F: Kind_cdc7cd43dac7585f + 'a,
137 {
138 #[document_signature]
143 #[document_parameters("The function to defer.", "The functor value.")]
145 #[document_returns("A `CoyonedaExplicit` wrapping the value with the deferred function.")]
147 #[document_examples]
148 pub fn new(
159 f: Func,
160 fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
161 ) -> Self {
162 CoyonedaExplicit {
163 fb,
164 func: f,
165 _phantom: PhantomData,
166 }
167 }
168
169 #[document_signature]
176 #[document_type_parameters("The new output type after applying the function.")]
178 #[document_parameters("The function to compose.")]
180 #[document_returns("A new `CoyonedaExplicit` with the composed function.")]
182 #[document_examples]
183 pub fn map<C: 'a>(
198 self,
199 f: impl Fn(A) -> C + 'a,
200 ) -> CoyonedaExplicit<'a, F, B, C, impl Fn(B) -> C + 'a> {
201 CoyonedaExplicit {
202 fb: self.fb,
203 func: compose(f, self.func),
204 _phantom: PhantomData,
205 }
206 }
207
208 #[document_signature]
213 #[document_returns("The underlying functor value with the composed function applied.")]
215 #[document_examples]
216 pub fn lower(self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
231 where
232 F: Functor, {
233 F::map(self.func, self.fb)
234 }
235
236 #[document_signature]
243 #[document_type_parameters("The brand of the target functor.")]
245 #[document_parameters("The natural transformation from `F` to `G`.")]
247 #[document_returns("A new `CoyonedaExplicit` over the target functor `G`.")]
249 #[document_examples]
250 pub fn hoist<G: Kind_cdc7cd43dac7585f + 'a>(
273 self,
274 nat: impl NaturalTransformation<F, G>,
275 ) -> CoyonedaExplicit<'a, G, B, A, Func> {
276 CoyonedaExplicit {
277 fb: nat.transform(self.fb),
278 func: self.func,
279 _phantom: PhantomData,
280 }
281 }
282
283 #[document_signature]
290 #[document_type_parameters(
292 "The brand of the cloneable function to use.",
293 "The monoid type to fold into."
294 )]
295 #[document_parameters("The function mapping each element to a monoid value.")]
297 #[document_returns("The combined monoid value.")]
299 #[document_examples]
300 pub fn fold_map<FnBrand, M>(
314 self,
315 func: impl Fn(A) -> M + 'a,
316 ) -> M
317 where
318 B: Clone,
319 M: Monoid + 'a,
320 F: Foldable,
321 FnBrand: LiftFn + 'a, {
322 F::fold_map::<FnBrand, B, M>(compose(func, self.func), self.fb)
323 }
324
325 #[document_signature]
333 #[document_type_parameters(
335 "The brand of the cloneable function to use.",
336 "The monoid type to fold into."
337 )]
338 #[document_parameters("The function mapping each index and element to a monoid value.")]
340 #[document_returns("The combined monoid value.")]
342 #[document_examples]
343 pub fn fold_map_with_index<FnBrand, M>(
357 self,
358 func: impl Fn(<F as WithIndex>::Index, A) -> M + 'a,
359 ) -> M
360 where
361 B: Clone,
362 M: Monoid + 'a,
363 F: FoldableWithIndex,
364 FnBrand: LiftFn + 'a,
365 <F as WithIndex>::Index: 'a, {
366 let f = self.func;
367 F::fold_map_with_index::<FnBrand, _, _>(move |i, b| func(i, f(b)), self.fb)
368 }
369
370 #[document_signature]
382 #[document_type_parameters(
384 "The applicative context brand.",
385 "The output element type after traversal."
386 )]
387 #[document_parameters(
389 "The function mapping each element to a value in the applicative context."
390 )]
391 #[document_returns(
393 "The traversed result wrapped in the applicative context, containing a `CoyonedaExplicit` in identity position."
394 )]
395 #[document_examples]
396 #[allow(
409 clippy::type_complexity,
410 reason = "HKT return type with nested Apply! is inherently complex"
411 )]
412 pub fn traverse<G: Applicative + 'a, C: 'a + Clone>(
413 self,
414 f: impl Fn(A) -> Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) + 'a,
415 ) -> Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, CoyonedaExplicit<'a, F, C, C, fn(C) -> C>>)
416 where
417 B: Clone,
418 F: Traversable,
419 Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone,
420 Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone, {
421 G::map(
422 |fc| CoyonedaExplicit::lift(fc),
423 F::traverse::<B, C, G>(compose(f, self.func), self.fb),
424 )
425 }
426
427 #[document_signature]
438 #[document_type_parameters(
440 "The brand of the cloneable function wrapper.",
441 "The intermediate type of the function container.",
442 "The output type after applying the function.",
443 "The type of the function in the function container."
444 )]
445 #[document_parameters(
447 "The `CoyonedaExplicit` containing the wrapped function.",
448 "The `CoyonedaExplicit` containing the value."
449 )]
450 #[document_returns(
452 "A `CoyonedaExplicit` containing the result of applying the function to the value."
453 )]
454 #[document_examples]
455 pub fn apply<
472 FnBrand: LiftFn + 'a,
473 Bf: 'a,
474 C: 'a,
475 FuncF: Fn(Bf) -> <FnBrand as CloneFn>::Of<'a, A, C> + 'a,
476 >(
477 ff: CoyonedaExplicit<'a, F, Bf, <FnBrand as CloneFn>::Of<'a, A, C>, FuncF>,
478 fa: Self,
479 ) -> CoyonedaExplicit<'a, F, C, C, fn(C) -> C>
480 where
481 A: Clone,
482 F: Semiapplicative, {
483 CoyonedaExplicit::lift(F::apply::<FnBrand, A, C>(ff.lower(), fa.lower()))
484 }
485
486 #[document_signature]
499 #[document_type_parameters("The output type of the bound computation.")]
501 #[document_parameters(
503 "The function to apply to each mapped value, returning a raw functor value."
504 )]
505 #[document_returns("A `CoyonedaExplicit` containing the bound result.")]
507 #[document_examples]
508 pub fn bind<C: 'a>(
520 self,
521 f: impl Fn(A) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) + 'a,
522 ) -> CoyonedaExplicit<'a, F, C, C, fn(C) -> C>
523 where
524 F: Semimonad, {
525 let func = self.func;
526 CoyonedaExplicit::lift(F::bind(self.fb, move |b| f(func(b))))
527 }
528
529 #[document_signature]
542 #[document_returns("A `BoxedCoyonedaExplicit` with the function boxed.")]
544 #[document_examples]
545 pub fn boxed(self) -> BoxedCoyonedaExplicit<'a, F, B, A> {
557 CoyonedaExplicit {
558 fb: self.fb,
559 func: Box::new(self.func),
560 _phantom: PhantomData,
561 }
562 }
563
564 #[document_signature]
569 #[document_returns("A `CoyonedaExplicit` with the function boxed as `Send`.")]
571 #[document_examples]
572 pub fn boxed_send(self) -> CoyonedaExplicit<'a, F, B, A, Box<dyn Fn(B) -> A + Send + 'a>>
584 where
585 Func: Send, {
586 CoyonedaExplicit {
587 fb: self.fb,
588 func: Box::new(self.func),
589 _phantom: PhantomData,
590 }
591 }
592 }
593
594 #[document_type_parameters(
595 "The lifetime of the values.",
596 "The brand of the underlying type constructor.",
597 "The type of the values in the functor."
598 )]
599 impl<'a, F, A: 'a> CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
600 where
601 F: Kind_cdc7cd43dac7585f + 'a,
602 {
603 #[document_signature]
608 #[document_parameters("The functor value to lift.")]
610 #[document_returns("A `CoyonedaExplicit` wrapping the value with the identity function.")]
612 #[document_examples]
613 pub fn lift(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> Self {
624 CoyonedaExplicit {
625 fb: fa,
626 func: identity as fn(A) -> A,
627 _phantom: PhantomData,
628 }
629 }
630 }
631
632 #[document_type_parameters(
633 "The lifetime of the values.",
634 "The brand of the underlying pointed functor.",
635 "The type of the value."
636 )]
637 impl<'a, F, A: 'a> CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
638 where
639 F: Pointed + 'a,
640 {
641 #[document_signature]
645 #[document_parameters("The value to wrap.")]
647 #[document_returns("A `CoyonedaExplicit` containing the pure value.")]
649 #[document_examples]
650 pub fn pure(a: A) -> Self {
661 Self::lift(F::pure(a))
662 }
663 }
664
665 #[document_type_parameters(
666 "The lifetime of the values.",
667 "The brand of the underlying type constructor.",
668 "The type of the values in the underlying functor.",
669 "The current output type.",
670 "The type of the accumulated function."
671 )]
672 impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> From<CoyonedaExplicit<'a, F, B, A, Func>>
673 for Coyoneda<'a, F, A>
674 where
675 F: Kind_cdc7cd43dac7585f + 'a,
676 {
677 #[document_signature]
688 #[document_parameters("The `CoyonedaExplicit` to convert.")]
690 #[document_returns("A `Coyoneda` wrapping the same value with the accumulated function.")]
692 #[document_examples]
693 fn from(explicit: CoyonedaExplicit<'a, F, B, A, Func>) -> Self {
705 Coyoneda::new(explicit.func, explicit.fb)
706 }
707 }
708
709 impl_kind! {
712 impl<F: Kind_cdc7cd43dac7585f + 'static, B: 'static> for CoyonedaExplicitBrand<F, B> {
713 type Of<'a, A: 'a>: 'a = BoxedCoyonedaExplicit<'a, F, B, A>;
714 }
715 }
716
717 #[document_type_parameters(
720 "The brand of the underlying type constructor.",
721 "The type of the values in the underlying functor."
722 )]
723 impl<F: Kind_cdc7cd43dac7585f + 'static, B: 'static> Functor for CoyonedaExplicitBrand<F, B> {
724 #[document_signature]
738 #[document_type_parameters(
740 "The lifetime of the values.",
741 "The type of the current output.",
742 "The type of the new output."
743 )]
744 #[document_parameters("The function to apply.", "The `BoxedCoyonedaExplicit` value.")]
746 #[document_returns("A new `BoxedCoyonedaExplicit` with the composed function.")]
748 #[document_examples]
749 fn map<'a, A: 'a, C: 'a>(
763 func: impl Fn(A) -> C + 'a,
764 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
765 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) {
766 fa.map(func).boxed()
767 }
768 }
769
770 #[document_type_parameters(
773 "The brand of the underlying foldable type constructor.",
774 "The type of the values in the underlying functor."
775 )]
776 impl<F: Kind_cdc7cd43dac7585f + Foldable + 'static, B: Clone + 'static> Foldable
777 for CoyonedaExplicitBrand<F, B>
778 {
779 #[document_signature]
787 #[document_type_parameters(
789 "The lifetime of the elements.",
790 "The brand of the cloneable function to use.",
791 "The type of the elements in the structure.",
792 "The type of the monoid."
793 )]
794 #[document_parameters(
796 "The function to map each element to a monoid.",
797 "The `BoxedCoyonedaExplicit` structure to fold."
798 )]
799 #[document_returns("The combined monoid value.")]
801 #[document_examples]
802 fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
819 func: impl Fn(A) -> M + 'a,
820 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
821 ) -> M
822 where
823 M: Monoid + 'a,
824 FnBrand: LiftFn + 'a, {
825 fa.fold_map::<FnBrand, M>(func)
826 }
827 }
828
829 #[document_type_parameters(
832 "The lifetime of the values.",
833 "The brand of the underlying type constructor.",
834 "The type of the values in the underlying functor.",
835 "The current output type.",
836 "The type of the accumulated function."
837 )]
838 #[document_parameters("The `CoyonedaExplicit` instance.")]
839 impl<'a, F, B: 'a, A: 'a, Func> core::fmt::Debug for CoyonedaExplicit<'a, F, B, A, Func>
840 where
841 F: Kind_cdc7cd43dac7585f + 'a,
842 Func: Fn(B) -> A + 'a,
843 {
844 #[document_signature]
849 #[document_parameters("The formatter.")]
851 #[document_returns("The formatting result.")]
853 #[document_examples]
854 fn fmt(
865 &self,
866 f: &mut core::fmt::Formatter<'_>,
867 ) -> core::fmt::Result {
868 f.write_str("CoyonedaExplicit(<opaque>)")
869 }
870 }
871}
872
873pub use inner::*;
874
875#[cfg(test)]
876mod tests {
877 use crate::{
878 brands::*,
879 classes::*,
880 functions::*,
881 types::*,
882 };
883
884 #[test]
885 fn lift_lower_identity_option() {
886 let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
887 assert_eq!(coyo.lower(), Some(42));
888 }
889
890 #[test]
891 fn lift_lower_identity_none() {
892 let coyo = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
893 assert_eq!(coyo.lower(), None);
894 }
895
896 #[test]
897 fn lift_lower_identity_vec() {
898 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
899 assert_eq!(coyo.lower(), vec![1, 2, 3]);
900 }
901
902 #[test]
903 fn new_constructor() {
904 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
905 assert_eq!(coyo.lower(), vec![2, 4, 6]);
906 }
907
908 #[test]
909 fn new_is_equivalent_to_lift_then_map() {
910 let f = |x: i32| x.to_string();
911 let v = vec![1, 2, 3];
912
913 let via_new = CoyonedaExplicit::<VecBrand, _, _, _>::new(f, v.clone()).lower();
914 let via_lift_map = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v).map(f).lower();
915
916 assert_eq!(via_new, via_lift_map);
917 }
918
919 #[test]
920 fn single_map_option() {
921 let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5)).map(|x| x * 2).lower();
922 assert_eq!(result, Some(10));
923 }
924
925 #[test]
926 fn chained_maps_vec() {
927 let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
928 .map(|x| x + 1)
929 .map(|x| x * 2)
930 .map(|x| x.to_string())
931 .lower();
932 assert_eq!(result, vec!["4", "6", "8"]);
933 }
934
935 #[test]
936 fn functor_identity_law() {
937 let result =
938 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(identity).lower();
939 assert_eq!(result, vec![1, 2, 3]);
940 }
941
942 #[test]
943 fn functor_composition_law() {
944 let f = |x: i32| x + 1;
945 let g = |x: i32| x * 2;
946
947 let left =
948 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(compose(f, g)).lower();
949
950 let right =
951 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(g).map(f).lower();
952
953 assert_eq!(left, right);
954 }
955
956 #[test]
957 fn many_chained_maps() {
958 let mut coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![0i64]).boxed();
959 for _ in 0 .. 100 {
960 coyo = coyo.map(|x| x + 1).boxed();
961 }
962 assert_eq!(coyo.lower(), vec![100i64]);
963 }
964
965 #[test]
966 fn map_on_none_stays_none() {
967 let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(None::<i32>)
968 .map(|x| x + 1)
969 .map(|x| x * 2)
970 .lower();
971 assert_eq!(result, None);
972 }
973
974 #[test]
975 fn lift_lower_roundtrip_preserves_value() {
976 let original = vec![10, 20, 30];
977 let roundtrip = CoyonedaExplicit::<VecBrand, _, _, _>::lift(original.clone()).lower();
978 assert_eq!(roundtrip, original);
979 }
980
981 #[test]
984 fn pure_option() {
985 let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::pure(42);
986 assert_eq!(coyo.lower(), Some(42));
987 }
988
989 #[test]
990 fn pure_vec() {
991 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::pure(42);
992 assert_eq!(coyo.lower(), vec![42]);
993 }
994
995 struct VecToOption;
998 impl NaturalTransformation<VecBrand, OptionBrand> for VecToOption {
999 fn transform<'a, A: 'a>(
1000 &self,
1001 fa: Vec<A>,
1002 ) -> Option<A> {
1003 fa.into_iter().next()
1004 }
1005 }
1006
1007 #[test]
1008 fn hoist_vec_to_option() {
1009 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![10, 20, 30]);
1010 let hoisted = coyo.hoist(VecToOption);
1011 assert_eq!(hoisted.lower(), Some(10));
1012 }
1013
1014 #[test]
1015 fn hoist_preserves_accumulated_maps() {
1016 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
1017 let hoisted = coyo.hoist(VecToOption);
1018 assert_eq!(hoisted.lower(), Some(10));
1019 }
1020
1021 #[test]
1022 fn hoist_then_map() {
1023 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![5, 10, 15]);
1024 let hoisted = coyo.hoist(VecToOption).map(|x: i32| x.to_string());
1025 assert_eq!(hoisted.lower(), Some("5".to_string()));
1026 }
1027
1028 #[test]
1031 fn fold_map_on_lifted_vec() {
1032 let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
1033 .fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
1034 assert_eq!(result, "123".to_string());
1035 }
1036
1037 #[test]
1038 fn fold_map_on_mapped_vec() {
1039 let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
1040 .map(|x| x * 10)
1041 .fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
1042 assert_eq!(result, "102030".to_string());
1043 }
1044
1045 #[test]
1046 fn fold_map_on_none_is_empty() {
1047 let result = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None)
1048 .map(|x| x + 1)
1049 .fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
1050 assert_eq!(result, String::new());
1051 }
1052
1053 #[test]
1056 fn traverse_vec_to_option_all_pass() {
1057 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
1058 let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
1059 coyo.traverse::<OptionBrand, _>(|x| if x > 0 { Some(x) } else { None });
1060 assert_eq!(result.map(|c| c.lower()), Some(vec![10, 20, 30]));
1061 }
1062
1063 #[test]
1064 fn traverse_vec_to_option_one_fails() {
1065 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, -2, 3]).map(|x| x * 10);
1066 let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
1067 coyo.traverse::<OptionBrand, _>(|x| if x > 0 { Some(x) } else { None });
1068 assert_eq!(result.map(|c| c.lower()), None);
1069 }
1070
1071 #[test]
1072 fn traverse_option_to_vec() {
1073 let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5)).map(|x| x * 2);
1074 let result: Vec<CoyonedaExplicit<OptionBrand, _, _, _>> =
1075 coyo.traverse::<VecBrand, _>(|x| vec![x, x + 1]);
1076 let lowered: Vec<Option<i32>> = result.into_iter().map(|c| c.lower()).collect();
1077 assert_eq!(lowered, vec![Some(10), Some(11)]);
1078 }
1079
1080 #[test]
1081 fn traverse_lifted_identity() {
1082 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
1083 let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
1084 coyo.traverse::<OptionBrand, _>(Some);
1085 assert_eq!(result.map(|c| c.lower()), Some(vec![1, 2, 3]));
1086 }
1087
1088 #[test]
1091 fn fold_map_with_index_on_vec() {
1092 let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
1093 .map(|x| x * 10)
1094 .fold_map_with_index::<RcFnBrand, _>(|i: usize, x: i32| format!("{i}:{x}"));
1095 assert_eq!(result, "0:101:202:30".to_string());
1096 }
1097
1098 #[test]
1099 fn fold_map_with_index_on_lifted_vec() {
1100 let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![10, 20, 30])
1101 .fold_map_with_index::<RcFnBrand, _>(|i: usize, x: i32| format!("{i}:{x}"));
1102 assert_eq!(result, "0:101:202:30".to_string());
1103 }
1104
1105 #[test]
1106 fn fold_map_with_index_on_option() {
1107 let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42))
1108 .map(|x| x + 1)
1109 .fold_map_with_index::<RcFnBrand, _>(|_: (), x: i32| x.to_string());
1110 assert_eq!(result, "43".to_string());
1111 }
1112
1113 #[test]
1114 fn fold_map_with_index_on_none() {
1115 let result = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None)
1116 .map(|x| x + 1)
1117 .fold_map_with_index::<RcFnBrand, _>(|_: (), x: i32| x.to_string());
1118 assert_eq!(result, String::new());
1119 }
1120
1121 #[test]
1124 fn into_coyoneda_preserves_semantics() {
1125 let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
1126 .map(|x| x + 1)
1127 .map(|x| x * 2);
1128 let coyo: Coyoneda<VecBrand, i32> = explicit.into();
1129 assert_eq!(coyo.lower(), vec![4, 6, 8]);
1130 }
1131
1132 #[test]
1133 fn into_coyoneda_from_lift() {
1134 let explicit = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
1135 let coyo: Coyoneda<OptionBrand, i32> = explicit.into();
1136 assert_eq!(coyo.lower(), Some(42));
1137 }
1138
1139 #[test]
1142 fn apply_some_to_some() {
1143 let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
1144 lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2),
1145 ));
1146 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
1147 let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
1148 assert_eq!(result, Some(10));
1149 }
1150
1151 #[test]
1152 fn apply_none_fn_to_some() {
1153 let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(
1154 None::<<RcFnBrand as CloneFn>::Of<'_, i32, i32>>,
1155 );
1156 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
1157 let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
1158 assert_eq!(result, None);
1159 }
1160
1161 #[test]
1162 fn apply_some_fn_to_none() {
1163 let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
1164 lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2),
1165 ));
1166 let fa = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
1167 let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
1168 assert_eq!(result, None);
1169 }
1170
1171 #[test]
1172 fn apply_vec_applies_each_fn_to_each_value() {
1173 let ff = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![
1174 lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
1175 lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 10),
1176 ]);
1177 let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![2i32, 3]);
1178 let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
1179 assert_eq!(result, vec![3, 4, 20, 30]);
1180 }
1181
1182 #[test]
1183 fn apply_preserves_prior_maps_on_fa() {
1184 let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
1185 lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
1186 ));
1187 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32)).map(|x| x * 2);
1189 let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
1190 assert_eq!(result, Some(11)); }
1192
1193 #[test]
1196 fn bind_some() {
1197 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
1198 let result = fa.bind(|x| Some(x * 2)).lower();
1199 assert_eq!(result, Some(10));
1200 }
1201
1202 #[test]
1203 fn bind_none_stays_none() {
1204 let fa = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
1205 let result = fa.bind(|x| Some(x * 2)).lower();
1206 assert_eq!(result, None);
1207 }
1208
1209 #[test]
1210 fn bind_returning_none_gives_none() {
1211 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
1212 let result = fa.bind(|_| None::<i32>).lower();
1213 assert_eq!(result, None);
1214 }
1215
1216 #[test]
1217 fn bind_vec() {
1218 let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1i32, 2, 3]);
1219 let result = fa.bind(|x| vec![x, x * 10]).lower();
1220 assert_eq!(result, vec![1, 10, 2, 20, 3, 30]);
1221 }
1222
1223 #[test]
1224 fn bind_uses_accumulated_maps() {
1225 let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(3i32)).map(|x| x * 2);
1226 let result = fa.bind(|x| Some(x + 1)).lower();
1227 assert_eq!(result, Some(7)); }
1229
1230 #[test]
1231 fn bind_vec_with_maps() {
1232 let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1i32, 2, 3]).map(|x| x * 2);
1233 let result = fa.bind(|x| vec![x, x + 1]).lower();
1234 assert_eq!(result, vec![2, 3, 4, 5, 6, 7]);
1235 }
1236
1237 #[test]
1240 fn from_explicit_to_coyoneda() {
1241 let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
1242 .map(|x| x + 1)
1243 .map(|x| x * 2);
1244 let coyo: Coyoneda<VecBrand, i32> = explicit.into();
1245 assert_eq!(coyo.lower(), vec![4, 6, 8]);
1246 }
1247
1248 #[test]
1249 fn from_explicit_lift_only() {
1250 let explicit = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
1251 let coyo: Coyoneda<OptionBrand, i32> = explicit.into();
1252 assert_eq!(coyo.lower(), Some(42));
1253 }
1254
1255 #[test]
1258 fn test_boxed_erases_type() {
1259 fn assert_same_type<T>(
1260 _a: &T,
1261 _b: &T,
1262 ) {
1263 }
1264 let a = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x + 1).boxed();
1265 let b = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![4, 5, 6]).map(|x| x * 2).boxed();
1266 assert_same_type(&a, &b);
1267 assert_eq!(a.lower(), vec![2, 3, 4]);
1268 assert_eq!(b.lower(), vec![8, 10, 12]);
1269 }
1270
1271 #[test]
1272 fn test_boxed_send() {
1273 fn assert_send<T: Send>(_: &T) {}
1274 let coyo =
1275 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x + 1).boxed_send();
1276 assert_send(&coyo);
1277 assert_eq!(coyo.lower(), vec![2, 3, 4]);
1278 }
1279
1280 #[test]
1281 fn test_send_auto_derived() {
1282 fn assert_send<T: Send>(_: &T) {}
1283 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
1285 assert_send(&coyo);
1286 }
1287
1288 #[test]
1291 fn brand_functor_map() {
1292 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
1293 let mapped =
1294 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(|x| x * 10, coyo);
1295 assert_eq!(mapped.lower(), vec![10, 20, 30]);
1296 }
1297
1298 #[test]
1299 fn brand_functor_identity_law() {
1300 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
1301 let result =
1302 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(identity, coyo)
1303 .lower();
1304 assert_eq!(result, vec![1, 2, 3]);
1305 }
1306
1307 #[test]
1308 fn brand_functor_composition_law() {
1309 let f = |x: i32| x + 1;
1310 let g = |x: i32| x * 2;
1311
1312 let coyo1 = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
1313 let left =
1314 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(compose(f, g), coyo1)
1315 .lower();
1316
1317 let coyo2 = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
1318 let right = explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
1319 f,
1320 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(g, coyo2),
1321 )
1322 .lower();
1323
1324 assert_eq!(left, right);
1325 }
1326
1327 #[test]
1328 fn brand_functor_chained_maps_fuse() {
1329 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
1331 let result = explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
1332 |x: i32| x.to_string(),
1333 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
1334 |x| x * 2,
1335 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(|x| x + 1, coyo),
1336 ),
1337 )
1338 .lower();
1339 assert_eq!(result, vec!["4", "6", "8"]);
1340 }
1341
1342 #[test]
1343 fn brand_foldable_fold_map() {
1344 let coyo =
1345 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10).boxed();
1346 let result =
1347 explicit::fold_map::<RcFnBrand, CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
1348 |x: i32| x.to_string(),
1349 coyo,
1350 );
1351 assert_eq!(result, "102030".to_string());
1352 }
1353
1354 #[test]
1355 fn brand_foldable_fold_right() {
1356 let coyo =
1357 CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 2).boxed();
1358 let result =
1359 explicit::fold_right::<RcFnBrand, CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
1360 |a: i32, b: i32| a + b,
1361 0,
1362 coyo,
1363 );
1364 assert_eq!(result, 12); }
1366
1367 #[test]
1368 fn brand_foldable_on_none() {
1369 let coyo = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None).map(|x| x + 1).boxed();
1370 let result =
1371 explicit::fold_map::<RcFnBrand, CoyonedaExplicitBrand<OptionBrand, i32>, _, _, _, _>(
1372 |x: i32| x.to_string(),
1373 coyo,
1374 );
1375 assert_eq!(result, String::new());
1376 }
1377
1378 mod property {
1381 use {
1382 crate::{
1383 brands::*,
1384 functions::*,
1385 types::*,
1386 },
1387 quickcheck_macros::quickcheck,
1388 };
1389
1390 #[quickcheck]
1391 fn functor_identity_vec(v: Vec<i32>) -> bool {
1392 let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v.clone()).boxed();
1393 explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(identity, coyo)
1394 .lower() == v
1395 }
1396
1397 #[quickcheck]
1398 fn functor_identity_option(x: Option<i32>) -> bool {
1399 let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(x).boxed();
1400 explicit::map::<CoyonedaExplicitBrand<OptionBrand, i32>, _, _, _, _>(identity, coyo)
1401 .lower() == x
1402 }
1403
1404 #[quickcheck]
1405 fn functor_composition_vec(v: Vec<i32>) -> bool {
1406 let f = |x: i32| x.wrapping_add(1);
1407 let g = |x: i32| x.wrapping_mul(2);
1408
1409 let left =
1410 CoyonedaExplicit::<VecBrand, _, _, _>::lift(v.clone()).map(compose(f, g)).lower();
1411 let right = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v).map(g).map(f).lower();
1412
1413 left == right
1414 }
1415 }
1416}