#[fp_macros::document_module]
mod inner {
use {
crate::{
brands::CoyonedaExplicitBrand,
classes::*,
functions::{
compose,
identity,
},
impl_kind,
kinds::*,
types::Coyoneda,
},
fp_macros::*,
std::marker::PhantomData,
};
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the values in the underlying functor (the input to the accumulated function).",
"The current output type (the output of the accumulated function).",
"The type of the accumulated function from `B` to `A`."
)]
pub struct CoyonedaExplicit<
'a,
F,
B: 'a,
A: 'a,
Func: Fn(B) -> A + 'a = Box<dyn Fn(B) -> A + 'a>,
>
where
F: Kind_cdc7cd43dac7585f + 'a, {
fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
func: Func,
_phantom: PhantomData<A>,
}
pub type BoxedCoyonedaExplicit<'a, F, B, A> =
CoyonedaExplicit<'a, F, B, A, Box<dyn Fn(B) -> A + 'a>>;
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the values in the underlying functor.",
"The current output type.",
"The type of the accumulated function."
)]
#[document_parameters("The `CoyonedaExplicit` instance.")]
impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaExplicit<'a, F, B, A, Func>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_parameters("The function to defer.", "The functor value.")]
#[document_returns("A `CoyonedaExplicit` wrapping the value with the deferred function.")]
#[document_examples]
pub fn new(
f: Func,
fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
) -> Self {
CoyonedaExplicit {
fb,
func: f,
_phantom: PhantomData,
}
}
#[document_signature]
#[document_type_parameters("The new output type after applying the function.")]
#[document_parameters("The function to compose.")]
#[document_returns("A new `CoyonedaExplicit` with the composed function.")]
#[document_examples]
pub fn map<C: 'a>(
self,
f: impl Fn(A) -> C + 'a,
) -> CoyonedaExplicit<'a, F, B, C, impl Fn(B) -> C + 'a> {
CoyonedaExplicit {
fb: self.fb,
func: compose(f, self.func),
_phantom: PhantomData,
}
}
#[document_signature]
#[document_returns("The underlying functor value with the composed function applied.")]
#[document_examples]
pub fn lower(self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor, {
F::map(self.func, self.fb)
}
#[document_signature]
#[document_type_parameters("The brand of the target functor.")]
#[document_parameters("The natural transformation from `F` to `G`.")]
#[document_returns("A new `CoyonedaExplicit` over the target functor `G`.")]
#[document_examples]
pub fn hoist<G: Kind_cdc7cd43dac7585f + 'a>(
self,
nat: impl NaturalTransformation<F, G>,
) -> CoyonedaExplicit<'a, G, B, A, Func> {
CoyonedaExplicit {
fb: nat.transform(self.fb),
func: self.func,
_phantom: PhantomData,
}
}
#[document_signature]
#[document_type_parameters(
"The brand of the cloneable function to use.",
"The monoid type to fold into."
)]
#[document_parameters("The function mapping each element to a monoid value.")]
#[document_returns("The combined monoid value.")]
#[document_examples]
pub fn fold_map<FnBrand, M>(
self,
func: impl Fn(A) -> M + 'a,
) -> M
where
B: Clone,
M: Monoid + 'a,
F: Foldable,
FnBrand: LiftFn + 'a, {
F::fold_map::<FnBrand, B, M>(compose(func, self.func), self.fb)
}
#[document_signature]
#[document_type_parameters(
"The brand of the cloneable function to use.",
"The monoid type to fold into."
)]
#[document_parameters("The function mapping each index and element to a monoid value.")]
#[document_returns("The combined monoid value.")]
#[document_examples]
pub fn fold_map_with_index<FnBrand, M>(
self,
func: impl Fn(<F as WithIndex>::Index, A) -> M + 'a,
) -> M
where
B: Clone,
M: Monoid + 'a,
F: FoldableWithIndex,
FnBrand: LiftFn + 'a,
<F as WithIndex>::Index: 'a, {
let f = self.func;
F::fold_map_with_index::<FnBrand, _, _>(move |i, b| func(i, f(b)), self.fb)
}
#[document_signature]
#[document_type_parameters(
"The applicative context brand.",
"The output element type after traversal."
)]
#[document_parameters(
"The function mapping each element to a value in the applicative context."
)]
#[document_returns(
"The traversed result wrapped in the applicative context, containing a `CoyonedaExplicit` in identity position."
)]
#[document_examples]
#[allow(
clippy::type_complexity,
reason = "HKT return type with nested Apply! is inherently complex"
)]
pub fn traverse<G: Applicative + 'a, C: 'a + Clone>(
self,
f: impl Fn(A) -> Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) + 'a,
) -> Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, CoyonedaExplicit<'a, F, C, C, fn(C) -> C>>)
where
B: Clone,
F: Traversable,
Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone,
Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone, {
G::map(
|fc| CoyonedaExplicit::lift(fc),
F::traverse::<B, C, G>(compose(f, self.func), self.fb),
)
}
#[document_signature]
#[document_type_parameters(
"The brand of the cloneable function wrapper.",
"The intermediate type of the function container.",
"The output type after applying the function.",
"The type of the function in the function container."
)]
#[document_parameters(
"The `CoyonedaExplicit` containing the wrapped function.",
"The `CoyonedaExplicit` containing the value."
)]
#[document_returns(
"A `CoyonedaExplicit` containing the result of applying the function to the value."
)]
#[document_examples]
pub fn apply<
FnBrand: LiftFn + 'a,
Bf: 'a,
C: 'a,
FuncF: Fn(Bf) -> <FnBrand as CloneFn>::Of<'a, A, C> + 'a,
>(
ff: CoyonedaExplicit<'a, F, Bf, <FnBrand as CloneFn>::Of<'a, A, C>, FuncF>,
fa: Self,
) -> CoyonedaExplicit<'a, F, C, C, fn(C) -> C>
where
A: Clone,
F: Semiapplicative, {
CoyonedaExplicit::lift(F::apply::<FnBrand, A, C>(ff.lower(), fa.lower()))
}
#[document_signature]
#[document_type_parameters("The output type of the bound computation.")]
#[document_parameters(
"The function to apply to each mapped value, returning a raw functor value."
)]
#[document_returns("A `CoyonedaExplicit` containing the bound result.")]
#[document_examples]
pub fn bind<C: 'a>(
self,
f: impl Fn(A) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) + 'a,
) -> CoyonedaExplicit<'a, F, C, C, fn(C) -> C>
where
F: Semimonad, {
let func = self.func;
CoyonedaExplicit::lift(F::bind(self.fb, move |b| f(func(b))))
}
#[document_signature]
#[document_returns("A `BoxedCoyonedaExplicit` with the function boxed.")]
#[document_examples]
pub fn boxed(self) -> BoxedCoyonedaExplicit<'a, F, B, A> {
CoyonedaExplicit {
fb: self.fb,
func: Box::new(self.func),
_phantom: PhantomData,
}
}
#[document_signature]
#[document_returns("A `CoyonedaExplicit` with the function boxed as `Send`.")]
#[document_examples]
pub fn boxed_send(self) -> CoyonedaExplicit<'a, F, B, A, Box<dyn Fn(B) -> A + Send + 'a>>
where
Func: Send, {
CoyonedaExplicit {
fb: self.fb,
func: Box::new(self.func),
_phantom: PhantomData,
}
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the values in the functor."
)]
impl<'a, F, A: 'a> CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_parameters("The functor value to lift.")]
#[document_returns("A `CoyonedaExplicit` wrapping the value with the identity function.")]
#[document_examples]
pub fn lift(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> Self {
CoyonedaExplicit {
fb: fa,
func: identity as fn(A) -> A,
_phantom: PhantomData,
}
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying pointed functor.",
"The type of the value."
)]
impl<'a, F, A: 'a> CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
where
F: Pointed + 'a,
{
#[document_signature]
#[document_parameters("The value to wrap.")]
#[document_returns("A `CoyonedaExplicit` containing the pure value.")]
#[document_examples]
pub fn pure(a: A) -> Self {
Self::lift(F::pure(a))
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the values in the underlying functor.",
"The current output type.",
"The type of the accumulated function."
)]
impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> From<CoyonedaExplicit<'a, F, B, A, Func>>
for Coyoneda<'a, F, A>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_parameters("The `CoyonedaExplicit` to convert.")]
#[document_returns("A `Coyoneda` wrapping the same value with the accumulated function.")]
#[document_examples]
fn from(explicit: CoyonedaExplicit<'a, F, B, A, Func>) -> Self {
Coyoneda::new(explicit.func, explicit.fb)
}
}
impl_kind! {
impl<F: Kind_cdc7cd43dac7585f + 'static, B: 'static> for CoyonedaExplicitBrand<F, B> {
type Of<'a, A: 'a>: 'a = BoxedCoyonedaExplicit<'a, F, B, A>;
}
}
#[document_type_parameters(
"The brand of the underlying type constructor.",
"The type of the values in the underlying functor."
)]
impl<F: Kind_cdc7cd43dac7585f + 'static, B: 'static> Functor for CoyonedaExplicitBrand<F, B> {
#[document_signature]
#[document_type_parameters(
"The lifetime of the values.",
"The type of the current output.",
"The type of the new output."
)]
#[document_parameters("The function to apply.", "The `BoxedCoyonedaExplicit` value.")]
#[document_returns("A new `BoxedCoyonedaExplicit` with the composed function.")]
#[document_examples]
fn map<'a, A: 'a, C: 'a>(
func: impl Fn(A) -> C + 'a,
fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) {
fa.map(func).boxed()
}
}
#[document_type_parameters(
"The brand of the underlying foldable type constructor.",
"The type of the values in the underlying functor."
)]
impl<F: Kind_cdc7cd43dac7585f + Foldable + 'static, B: Clone + 'static> Foldable
for CoyonedaExplicitBrand<F, B>
{
#[document_signature]
#[document_type_parameters(
"The lifetime of the elements.",
"The brand of the cloneable function to use.",
"The type of the elements in the structure.",
"The type of the monoid."
)]
#[document_parameters(
"The function to map each element to a monoid.",
"The `BoxedCoyonedaExplicit` structure to fold."
)]
#[document_returns("The combined monoid value.")]
#[document_examples]
fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
func: impl Fn(A) -> M + 'a,
fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
) -> M
where
M: Monoid + 'a,
FnBrand: LiftFn + 'a, {
fa.fold_map::<FnBrand, M>(func)
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the values in the underlying functor.",
"The current output type.",
"The type of the accumulated function."
)]
#[document_parameters("The `CoyonedaExplicit` instance.")]
impl<'a, F, B: 'a, A: 'a, Func> core::fmt::Debug for CoyonedaExplicit<'a, F, B, A, Func>
where
F: Kind_cdc7cd43dac7585f + 'a,
Func: Fn(B) -> A + 'a,
{
#[document_signature]
#[document_parameters("The formatter.")]
#[document_returns("The formatting result.")]
#[document_examples]
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
f.write_str("CoyonedaExplicit(<opaque>)")
}
}
}
pub use inner::*;
#[cfg(test)]
mod tests {
use crate::{
brands::*,
classes::*,
functions::*,
types::*,
};
#[test]
fn lift_lower_identity_option() {
let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn lift_lower_identity_none() {
let coyo = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
assert_eq!(coyo.lower(), None);
}
#[test]
fn lift_lower_identity_vec() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
assert_eq!(coyo.lower(), vec![1, 2, 3]);
}
#[test]
fn new_constructor() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
assert_eq!(coyo.lower(), vec![2, 4, 6]);
}
#[test]
fn new_is_equivalent_to_lift_then_map() {
let f = |x: i32| x.to_string();
let v = vec![1, 2, 3];
let via_new = CoyonedaExplicit::<VecBrand, _, _, _>::new(f, v.clone()).lower();
let via_lift_map = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v).map(f).lower();
assert_eq!(via_new, via_lift_map);
}
#[test]
fn single_map_option() {
let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5)).map(|x| x * 2).lower();
assert_eq!(result, Some(10));
}
#[test]
fn chained_maps_vec() {
let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.map(|x| x + 1)
.map(|x| x * 2)
.map(|x| x.to_string())
.lower();
assert_eq!(result, vec!["4", "6", "8"]);
}
#[test]
fn functor_identity_law() {
let result =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(identity).lower();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn functor_composition_law() {
let f = |x: i32| x + 1;
let g = |x: i32| x * 2;
let left =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(compose(f, g)).lower();
let right =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(g).map(f).lower();
assert_eq!(left, right);
}
#[test]
fn many_chained_maps() {
let mut coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![0i64]).boxed();
for _ in 0 .. 100 {
coyo = coyo.map(|x| x + 1).boxed();
}
assert_eq!(coyo.lower(), vec![100i64]);
}
#[test]
fn map_on_none_stays_none() {
let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(None::<i32>)
.map(|x| x + 1)
.map(|x| x * 2)
.lower();
assert_eq!(result, None);
}
#[test]
fn lift_lower_roundtrip_preserves_value() {
let original = vec![10, 20, 30];
let roundtrip = CoyonedaExplicit::<VecBrand, _, _, _>::lift(original.clone()).lower();
assert_eq!(roundtrip, original);
}
#[test]
fn pure_option() {
let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::pure(42);
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn pure_vec() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::pure(42);
assert_eq!(coyo.lower(), vec![42]);
}
struct VecToOption;
impl NaturalTransformation<VecBrand, OptionBrand> for VecToOption {
fn transform<'a, A: 'a>(
&self,
fa: Vec<A>,
) -> Option<A> {
fa.into_iter().next()
}
}
#[test]
fn hoist_vec_to_option() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![10, 20, 30]);
let hoisted = coyo.hoist(VecToOption);
assert_eq!(hoisted.lower(), Some(10));
}
#[test]
fn hoist_preserves_accumulated_maps() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
let hoisted = coyo.hoist(VecToOption);
assert_eq!(hoisted.lower(), Some(10));
}
#[test]
fn hoist_then_map() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![5, 10, 15]);
let hoisted = coyo.hoist(VecToOption).map(|x: i32| x.to_string());
assert_eq!(hoisted.lower(), Some("5".to_string()));
}
#[test]
fn fold_map_on_lifted_vec() {
let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
assert_eq!(result, "123".to_string());
}
#[test]
fn fold_map_on_mapped_vec() {
let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.map(|x| x * 10)
.fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
assert_eq!(result, "102030".to_string());
}
#[test]
fn fold_map_on_none_is_empty() {
let result = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None)
.map(|x| x + 1)
.fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
assert_eq!(result, String::new());
}
#[test]
fn traverse_vec_to_option_all_pass() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
coyo.traverse::<OptionBrand, _>(|x| if x > 0 { Some(x) } else { None });
assert_eq!(result.map(|c| c.lower()), Some(vec![10, 20, 30]));
}
#[test]
fn traverse_vec_to_option_one_fails() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, -2, 3]).map(|x| x * 10);
let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
coyo.traverse::<OptionBrand, _>(|x| if x > 0 { Some(x) } else { None });
assert_eq!(result.map(|c| c.lower()), None);
}
#[test]
fn traverse_option_to_vec() {
let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5)).map(|x| x * 2);
let result: Vec<CoyonedaExplicit<OptionBrand, _, _, _>> =
coyo.traverse::<VecBrand, _>(|x| vec![x, x + 1]);
let lowered: Vec<Option<i32>> = result.into_iter().map(|c| c.lower()).collect();
assert_eq!(lowered, vec![Some(10), Some(11)]);
}
#[test]
fn traverse_lifted_identity() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
let result: Option<CoyonedaExplicit<VecBrand, _, _, _>> =
coyo.traverse::<OptionBrand, _>(Some);
assert_eq!(result.map(|c| c.lower()), Some(vec![1, 2, 3]));
}
#[test]
fn fold_map_with_index_on_vec() {
let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.map(|x| x * 10)
.fold_map_with_index::<RcFnBrand, _>(|i: usize, x: i32| format!("{i}:{x}"));
assert_eq!(result, "0:101:202:30".to_string());
}
#[test]
fn fold_map_with_index_on_lifted_vec() {
let result = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![10, 20, 30])
.fold_map_with_index::<RcFnBrand, _>(|i: usize, x: i32| format!("{i}:{x}"));
assert_eq!(result, "0:101:202:30".to_string());
}
#[test]
fn fold_map_with_index_on_option() {
let result = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42))
.map(|x| x + 1)
.fold_map_with_index::<RcFnBrand, _>(|_: (), x: i32| x.to_string());
assert_eq!(result, "43".to_string());
}
#[test]
fn fold_map_with_index_on_none() {
let result = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None)
.map(|x| x + 1)
.fold_map_with_index::<RcFnBrand, _>(|_: (), x: i32| x.to_string());
assert_eq!(result, String::new());
}
#[test]
fn into_coyoneda_preserves_semantics() {
let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.map(|x| x + 1)
.map(|x| x * 2);
let coyo: Coyoneda<VecBrand, i32> = explicit.into();
assert_eq!(coyo.lower(), vec![4, 6, 8]);
}
#[test]
fn into_coyoneda_from_lift() {
let explicit = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
let coyo: Coyoneda<OptionBrand, i32> = explicit.into();
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn apply_some_to_some() {
let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2),
));
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
assert_eq!(result, Some(10));
}
#[test]
fn apply_none_fn_to_some() {
let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(
None::<<RcFnBrand as CloneFn>::Of<'_, i32, i32>>,
);
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
assert_eq!(result, None);
}
#[test]
fn apply_some_fn_to_none() {
let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2),
));
let fa = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
assert_eq!(result, None);
}
#[test]
fn apply_vec_applies_each_fn_to_each_value() {
let ff = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 10),
]);
let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![2i32, 3]);
let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
assert_eq!(result, vec![3, 4, 20, 30]);
}
#[test]
fn apply_preserves_prior_maps_on_fa() {
let ff = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
));
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32)).map(|x| x * 2);
let result = CoyonedaExplicit::apply::<RcFnBrand, _, _, _>(ff, fa).lower();
assert_eq!(result, Some(11)); }
#[test]
fn bind_some() {
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
let result = fa.bind(|x| Some(x * 2)).lower();
assert_eq!(result, Some(10));
}
#[test]
fn bind_none_stays_none() {
let fa = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None);
let result = fa.bind(|x| Some(x * 2)).lower();
assert_eq!(result, None);
}
#[test]
fn bind_returning_none_gives_none() {
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(5i32));
let result = fa.bind(|_| None::<i32>).lower();
assert_eq!(result, None);
}
#[test]
fn bind_vec() {
let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1i32, 2, 3]);
let result = fa.bind(|x| vec![x, x * 10]).lower();
assert_eq!(result, vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn bind_uses_accumulated_maps() {
let fa = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(3i32)).map(|x| x * 2);
let result = fa.bind(|x| Some(x + 1)).lower();
assert_eq!(result, Some(7)); }
#[test]
fn bind_vec_with_maps() {
let fa = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1i32, 2, 3]).map(|x| x * 2);
let result = fa.bind(|x| vec![x, x + 1]).lower();
assert_eq!(result, vec![2, 3, 4, 5, 6, 7]);
}
#[test]
fn from_explicit_to_coyoneda() {
let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3])
.map(|x| x + 1)
.map(|x| x * 2);
let coyo: Coyoneda<VecBrand, i32> = explicit.into();
assert_eq!(coyo.lower(), vec![4, 6, 8]);
}
#[test]
fn from_explicit_lift_only() {
let explicit = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(Some(42));
let coyo: Coyoneda<OptionBrand, i32> = explicit.into();
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn test_boxed_erases_type() {
fn assert_same_type<T>(
_a: &T,
_b: &T,
) {
}
let a = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x + 1).boxed();
let b = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![4, 5, 6]).map(|x| x * 2).boxed();
assert_same_type(&a, &b);
assert_eq!(a.lower(), vec![2, 3, 4]);
assert_eq!(b.lower(), vec![8, 10, 12]);
}
#[test]
fn test_boxed_send() {
fn assert_send<T: Send>(_: &T) {}
let coyo =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x + 1).boxed_send();
assert_send(&coyo);
assert_eq!(coyo.lower(), vec![2, 3, 4]);
}
#[test]
fn test_send_auto_derived() {
fn assert_send<T: Send>(_: &T) {}
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]);
assert_send(&coyo);
}
#[test]
fn brand_functor_map() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
let mapped =
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(|x| x * 10, coyo);
assert_eq!(mapped.lower(), vec![10, 20, 30]);
}
#[test]
fn brand_functor_identity_law() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
let result =
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(identity, coyo)
.lower();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn brand_functor_composition_law() {
let f = |x: i32| x + 1;
let g = |x: i32| x * 2;
let coyo1 = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
let left =
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(compose(f, g), coyo1)
.lower();
let coyo2 = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
let right = explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
f,
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(g, coyo2),
)
.lower();
assert_eq!(left, right);
}
#[test]
fn brand_functor_chained_maps_fuse() {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).boxed();
let result = explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
|x: i32| x.to_string(),
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
|x| x * 2,
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(|x| x + 1, coyo),
),
)
.lower();
assert_eq!(result, vec!["4", "6", "8"]);
}
#[test]
fn brand_foldable_fold_map() {
let coyo =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 10).boxed();
let result =
explicit::fold_map::<RcFnBrand, CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
|x: i32| x.to_string(),
coyo,
);
assert_eq!(result, "102030".to_string());
}
#[test]
fn brand_foldable_fold_right() {
let coyo =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(vec![1, 2, 3]).map(|x| x * 2).boxed();
let result =
explicit::fold_right::<RcFnBrand, CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(
|a: i32, b: i32| a + b,
0,
coyo,
);
assert_eq!(result, 12); }
#[test]
fn brand_foldable_on_none() {
let coyo = CoyonedaExplicit::<OptionBrand, i32, i32, _>::lift(None).map(|x| x + 1).boxed();
let result =
explicit::fold_map::<RcFnBrand, CoyonedaExplicitBrand<OptionBrand, i32>, _, _, _, _>(
|x: i32| x.to_string(),
coyo,
);
assert_eq!(result, String::new());
}
mod property {
use {
crate::{
brands::*,
functions::*,
types::*,
},
quickcheck_macros::quickcheck,
};
#[quickcheck]
fn functor_identity_vec(v: Vec<i32>) -> bool {
let coyo = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v.clone()).boxed();
explicit::map::<CoyonedaExplicitBrand<VecBrand, i32>, _, _, _, _>(identity, coyo)
.lower() == v
}
#[quickcheck]
fn functor_identity_option(x: Option<i32>) -> bool {
let coyo = CoyonedaExplicit::<OptionBrand, _, _, _>::lift(x).boxed();
explicit::map::<CoyonedaExplicitBrand<OptionBrand, i32>, _, _, _, _>(identity, coyo)
.lower() == x
}
#[quickcheck]
fn functor_composition_vec(v: Vec<i32>) -> bool {
let f = |x: i32| x.wrapping_add(1);
let g = |x: i32| x.wrapping_mul(2);
let left =
CoyonedaExplicit::<VecBrand, _, _, _>::lift(v.clone()).map(compose(f, g)).lower();
let right = CoyonedaExplicit::<VecBrand, _, _, _>::lift(v).map(g).map(f).lower();
left == right
}
}
}