#[fp_macros::document_module]
mod inner {
use {
crate::{
Apply,
brands::CoyonedaBrand,
classes::*,
impl_kind,
kinds::*,
types::CoyonedaExplicit,
},
fp_macros::*,
};
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The output type of the accumulated mapping function."
)]
#[document_parameters("The boxed trait object to consume.")]
trait CoyonedaInner<'a, F, A: 'a>: 'a
where
F: Kind_cdc7cd43dac7585f + 'a, {
#[document_signature]
#[document_returns("The underlying functor value with accumulated functions applied.")]
#[document_examples]
fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor;
}
struct CoyonedaBase<'a, F, A: 'a>
where
F: Kind_cdc7cd43dac7585f + 'a, {
fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The type of the value inside the functor."
)]
#[document_parameters("The base layer instance.")]
impl<'a, F, A: 'a> CoyonedaInner<'a, F, A> for CoyonedaBase<'a, F, A>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_returns("The underlying functor value, unchanged.")]
#[document_examples]
fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor, {
self.fa
}
}
struct CoyonedaMapLayer<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a>
where
F: Kind_cdc7cd43dac7585f + 'a, {
inner: Box<dyn CoyonedaInner<'a, F, B> + 'a>,
func: Func,
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The input type of this layer's mapping function.",
"The output type of this layer's mapping function.",
"The type of this layer's mapping function."
)]
#[document_parameters("The map layer instance.")]
impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaInner<'a, F, A>
for CoyonedaMapLayer<'a, F, B, A, Func>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_returns("The underlying functor value with this layer's function applied.")]
#[document_examples]
fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor, {
#[cfg(feature = "stacker")]
{
stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
let lowered = self.inner.lower();
F::map(self.func, lowered)
})
}
#[cfg(not(feature = "stacker"))]
{
let lowered = self.inner.lower();
F::map(self.func, lowered)
}
}
}
struct CoyonedaNewLayer<'a, F, B: 'a, A: 'a, Func: 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,
}
#[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 output type of the mapping function.",
"The type of the mapping function."
)]
#[document_parameters("The new layer instance.")]
impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaInner<'a, F, A>
for CoyonedaNewLayer<'a, F, B, A, Func>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_returns("The underlying functor value with the function applied.")]
#[document_examples]
fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor, {
F::map(self.func, self.fb)
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The current output type."
)]
pub struct Coyoneda<'a, F, A: 'a>(Box<dyn CoyonedaInner<'a, F, A> + 'a>)
where
F: Kind_cdc7cd43dac7585f + 'a;
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The current output type."
)]
#[document_parameters("The `Coyoneda` instance.")]
impl<'a, F, A: 'a> Coyoneda<'a, F, A>
where
F: Kind_cdc7cd43dac7585f + 'a,
{
#[document_signature]
#[document_type_parameters("The type of the values in the underlying functor.")]
#[document_parameters("The function to defer.", "The functor value.")]
#[document_returns("A `Coyoneda` wrapping the value with the deferred function.")]
#[document_examples]
pub fn new<B: 'a>(
f: impl Fn(B) -> A + 'a,
fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
) -> Self {
Coyoneda(Box::new(CoyonedaNewLayer {
fb,
func: f,
}))
}
#[document_signature]
#[document_parameters("The functor value to lift.")]
#[document_returns("A `Coyoneda` wrapping the value.")]
#[document_examples]
pub fn lift(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> Self {
Coyoneda(Box::new(CoyonedaBase {
fa,
}))
}
#[document_signature]
#[document_returns("The underlying functor value with all accumulated functions applied.")]
#[document_examples]
pub fn lower(self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
where
F: Functor, {
self.0.lower()
}
#[document_signature]
#[document_returns("A fresh `Coyoneda` with a single base layer.")]
#[document_examples]
pub fn collapse(self) -> Self
where
F: Functor, {
Coyoneda::lift(self.lower())
}
#[document_signature]
#[document_type_parameters("The new output type after applying the function.")]
#[document_parameters("The function to apply.")]
#[document_returns("A new `Coyoneda` with the function stored for deferred application.")]
#[document_examples]
pub fn map<B: 'a>(
self,
f: impl Fn(A) -> B + 'a,
) -> Coyoneda<'a, F, B> {
Coyoneda(Box::new(CoyonedaMapLayer {
inner: self.0,
func: f,
}))
}
#[document_signature]
#[document_type_parameters("The brand of the target functor.")]
#[document_parameters("The natural transformation from `F` to `G`.")]
#[document_returns("A new `Coyoneda` over the target functor `G`.")]
#[document_examples]
pub fn hoist<G: Kind_cdc7cd43dac7585f + 'a>(
self,
nat: impl NaturalTransformation<F, G>,
) -> Coyoneda<'a, G, A>
where
F: Functor, {
Coyoneda::lift(nat.transform(self.lower()))
}
}
impl_kind! {
impl<F: Kind_cdc7cd43dac7585f + 'static> for CoyonedaBrand<F> {
type Of<'a, A: 'a>: 'a = Coyoneda<'a, F, A>;
}
}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Kind_cdc7cd43dac7585f + 'static> Functor for CoyonedaBrand<F> {
#[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 `Coyoneda` value.")]
#[document_returns("A new `Coyoneda` with the function stored for deferred application.")]
#[document_examples]
fn map<'a, A: 'a, B: 'a>(
func: impl Fn(A) -> B + '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, B>) {
fa.map(func)
}
}
#[document_type_parameters("The brand of the underlying pointed functor.")]
impl<F: Pointed + 'static> Pointed for CoyonedaBrand<F> {
#[document_signature]
#[document_type_parameters("The lifetime of the value.", "The type of the value to wrap.")]
#[document_parameters("The value to wrap.")]
#[document_returns("A `Coyoneda` containing the pure value.")]
#[document_examples]
fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
Coyoneda::lift(F::pure(a))
}
}
#[document_type_parameters("The brand of the underlying foldable functor.")]
impl<F: Functor + Foldable + 'static> Foldable for CoyonedaBrand<F> {
#[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 `Coyoneda` 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, {
F::fold_map::<FnBrand, A, M>(func, fa.lower())
}
}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Functor + Lift + 'static> Lift for CoyonedaBrand<F> {
#[document_signature]
#[document_type_parameters(
"The lifetime of the values.",
"The type of the first value.",
"The type of the second value.",
"The type of the result."
)]
#[document_parameters(
"The binary function to apply.",
"The first `Coyoneda` value.",
"The second `Coyoneda` value."
)]
#[document_returns("A `Coyoneda` containing the result of applying the function.")]
#[document_examples]
fn lift2<'a, A, B, C>(
func: impl Fn(A, B) -> C + 'a,
fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
where
A: Clone + 'a,
B: Clone + 'a,
C: 'a, {
Coyoneda::lift(F::lift2(func, fa.lower(), fb.lower()))
}
}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Functor + Lift + 'static> ApplyFirst for CoyonedaBrand<F> {}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Functor + Lift + 'static> ApplySecond for CoyonedaBrand<F> {}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Functor + Semiapplicative + 'static> Semiapplicative for CoyonedaBrand<F> {
#[document_signature]
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the cloneable function wrapper.",
"The type of the input value.",
"The type of the output value."
)]
#[document_parameters(
"The `Coyoneda` containing the function(s).",
"The `Coyoneda` containing the value(s)."
)]
#[document_returns("A `Coyoneda` containing the result(s) of applying the function(s).")]
#[document_examples]
fn apply<'a, FnBrand: 'a + CloneFn, A: 'a + Clone, B: 'a>(
ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneFn>::Of<'a, A, B>>),
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, B>) {
Coyoneda::lift(F::apply::<FnBrand, A, B>(ff.lower(), fa.lower()))
}
}
#[document_type_parameters("The brand of the underlying type constructor.")]
impl<F: Functor + Semimonad + 'static> Semimonad for CoyonedaBrand<F> {
#[document_signature]
#[document_type_parameters(
"The lifetime of the values.",
"The type of the value in the input `Coyoneda`.",
"The type of the value in the output `Coyoneda`."
)]
#[document_parameters(
"The input `Coyoneda` value.",
"The function to apply to the inner value, returning a new `Coyoneda`."
)]
#[document_returns("A new `Coyoneda` containing the bound result.")]
#[document_examples]
fn bind<'a, A: 'a, B: 'a>(
ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
func: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
Coyoneda::lift(F::bind(ma.lower(), move |a| func(a).lower()))
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying foldable functor.",
"The type of the values."
)]
impl<'a, F, A: 'a> From<Coyoneda<'a, F, A>> for CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
where
F: Kind_cdc7cd43dac7585f + Functor + 'a,
{
#[document_signature]
#[document_parameters("The `Coyoneda` to convert.")]
#[document_returns(
"A `CoyonedaExplicit` in identity position (B = A) wrapping the lowered value."
)]
#[document_examples]
fn from(coyo: Coyoneda<'a, F, A>) -> Self {
CoyonedaExplicit::lift(coyo.lower())
}
}
#[document_type_parameters(
"The lifetime of the values.",
"The brand of the underlying type constructor.",
"The current output type."
)]
#[document_parameters("The `Coyoneda` instance.")]
impl<'a, F, A: 'a> core::fmt::Debug for Coyoneda<'a, F, A>
where
F: Kind_cdc7cd43dac7585f + '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("Coyoneda(<opaque>)")
}
}
}
pub use inner::*;
#[cfg(test)]
mod tests {
use crate::{
brands::*,
classes::*,
functions::*,
types::*,
};
#[test]
fn lift_lower_identity_option() {
let coyo = Coyoneda::<OptionBrand, _>::lift(Some(42));
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn lift_lower_identity_none() {
let coyo = Coyoneda::<OptionBrand, i32>::lift(None);
assert_eq!(coyo.lower(), None);
}
#[test]
fn lift_lower_identity_vec() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
assert_eq!(coyo.lower(), vec![1, 2, 3]);
}
#[test]
fn new_constructor() {
let coyo = Coyoneda::<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 = Coyoneda::<VecBrand, _>::new(f, v.clone()).lower();
let via_lift_map = Coyoneda::<VecBrand, _>::lift(v).map(f).lower();
assert_eq!(via_new, via_lift_map);
}
#[test]
fn single_map_option() {
let result = Coyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x * 2).lower();
assert_eq!(result, Some(10));
}
#[test]
fn chained_maps_vec() {
let result = Coyoneda::<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 coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
let result = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(identity, coyo).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 coyo1 = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
let left =
explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(compose(f, g), coyo1).lower();
let coyo2 = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
let right = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
f,
explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(g, coyo2),
)
.lower();
assert_eq!(left, right);
}
#[test]
fn many_chained_maps() {
let mut coyo = Coyoneda::<VecBrand, _>::lift(vec![0i64]);
for _ in 0 .. 100 {
coyo = coyo.map(|x| x + 1);
}
assert_eq!(coyo.lower(), vec![100i64]);
}
#[test]
fn collapse_preserves_value() {
let coyo =
Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).map(|x| x * 2).collapse();
assert_eq!(coyo.lower(), vec![4, 6, 8]);
}
#[test]
fn collapse_then_map() {
let coyo =
Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).collapse().map(|x| x * 2);
assert_eq!(coyo.lower(), vec![4, 6, 8]);
}
#[test]
fn collapse_periodic_in_loop() {
let mut coyo = Coyoneda::<VecBrand, _>::lift(vec![0i64]);
for i in 0 .. 50 {
coyo = coyo.map(|x| x + 1);
if i % 25 == 24 {
coyo = coyo.collapse();
}
}
assert_eq!(coyo.lower(), vec![50i64]);
}
#[test]
fn map_on_none_stays_none() {
let result =
Coyoneda::<OptionBrand, _>::lift(None::<i32>).map(|x| x + 1).map(|x| x * 2).lower();
assert_eq!(result, None);
}
#[test]
fn map_free_function_dispatches_to_brand() {
let coyo = Coyoneda::<OptionBrand, _>::lift(Some(10));
let result: Coyoneda<OptionBrand, String> =
explicit::map::<CoyonedaBrand<OptionBrand>, _, _, _, _>(|x: i32| x.to_string(), coyo);
assert_eq!(result.lower(), Some("10".to_string()));
}
#[test]
fn lift_lower_roundtrip_preserves_value() {
let original = vec![10, 20, 30];
let roundtrip = Coyoneda::<VecBrand, _>::lift(original.clone()).lower();
assert_eq!(roundtrip, original);
}
#[test]
fn pointed_pure_option() {
let coyo: Coyoneda<OptionBrand, i32> = pure::<CoyonedaBrand<OptionBrand>, _>(42);
assert_eq!(coyo.lower(), Some(42));
}
#[test]
fn pointed_pure_vec() {
let coyo: Coyoneda<VecBrand, i32> = pure::<CoyonedaBrand<VecBrand>, _>(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 = Coyoneda::<VecBrand, _>::lift(vec![10, 20, 30]);
let hoisted: Coyoneda<OptionBrand, i32> = coyo.hoist(VecToOption);
assert_eq!(hoisted.lower(), Some(10));
}
#[test]
fn hoist_with_maps() {
let coyo = Coyoneda::<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 = Coyoneda::<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 coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
|x: i32| x.to_string(),
coyo,
);
assert_eq!(result, "123".to_string());
}
#[test]
fn fold_map_on_mapped_coyoneda() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
|x: i32| x.to_string(),
coyo,
);
assert_eq!(result, "102030".to_string());
}
#[test]
fn fold_right_on_coyoneda() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 2);
let result = explicit::fold_right::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
|a: i32, b: i32| a + b,
0,
coyo,
);
assert_eq!(result, 12); }
#[test]
fn fold_left_on_coyoneda() {
let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x + 1);
let result = explicit::fold_left::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _, _, _>(
|acc: i32, a: i32| acc + a,
10,
coyo,
);
assert_eq!(result, 16); }
#[test]
fn fold_map_on_none_is_empty() {
let coyo = Coyoneda::<OptionBrand, i32>::lift(None).map(|x| x + 1);
let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _, _, _>(
|x: i32| x.to_string(),
coyo,
);
assert_eq!(result, String::new());
}
#[test]
fn lift2_option_both_some() {
let a = Coyoneda::<OptionBrand, _>::lift(Some(3));
let b = Coyoneda::<OptionBrand, _>::lift(Some(4));
let result =
explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
assert_eq!(result.lower(), Some(7));
}
#[test]
fn lift2_option_one_none() {
let a = Coyoneda::<OptionBrand, _>::lift(Some(3));
let b = Coyoneda::<OptionBrand, i32>::lift(None);
let result =
explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
assert_eq!(result.lower(), None);
}
#[test]
fn lift2_vec() {
let a = Coyoneda::<VecBrand, _>::lift(vec![1, 2]);
let b = Coyoneda::<VecBrand, _>::lift(vec![10, 20]);
let result =
explicit::lift2::<CoyonedaBrand<VecBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
assert_eq!(result.lower(), vec![11, 21, 12, 22]);
}
#[test]
fn lift2_with_prior_maps() {
let a = Coyoneda::<OptionBrand, _>::lift(Some(3)).map(|x| x * 2);
let b = Coyoneda::<OptionBrand, _>::lift(Some(4)).map(|x| x + 1);
let result =
explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
assert_eq!(result.lower(), Some(11)); }
#[test]
fn apply_option_some() {
let ff =
Coyoneda::<OptionBrand, _>::lift(Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2)));
let fa = Coyoneda::<OptionBrand, _>::lift(Some(5));
let result = apply(ff, fa);
assert_eq!(result.lower(), Some(10));
}
#[test]
fn apply_option_none_fn() {
let ff = Coyoneda::<OptionBrand, _>::lift(None::<<RcFnBrand as CloneFn>::Of<'_, i32, i32>>);
let fa = Coyoneda::<OptionBrand, _>::lift(Some(5));
let result = apply(ff, fa);
assert_eq!(result.lower(), None);
}
#[test]
fn apply_vec() {
let ff = Coyoneda::<VecBrand, _>::lift(vec![
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 10),
]);
let fa = Coyoneda::<VecBrand, _>::lift(vec![2i32, 3]);
let result = apply(ff, fa);
assert_eq!(result.lower(), vec![3, 4, 20, 30]);
}
#[test]
fn bind_option_some() {
let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5));
let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
Coyoneda::<OptionBrand, _>::lift(Some(x * 2))
});
assert_eq!(result.lower(), Some(10));
}
#[test]
fn bind_option_none() {
let coyo = Coyoneda::<OptionBrand, i32>::lift(None);
let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
Coyoneda::<OptionBrand, _>::lift(Some(x * 2))
});
assert_eq!(result.lower(), None);
}
#[test]
fn bind_vec() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1i32, 2, 3]);
let result = explicit::bind::<CoyonedaBrand<VecBrand>, _, _, _, _>(coyo, |x| {
Coyoneda::<VecBrand, _>::lift(vec![x, x * 10])
});
assert_eq!(result.lower(), vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn bind_after_map() {
let coyo = Coyoneda::<OptionBrand, _>::lift(Some(3)).map(|x| x * 2);
let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
Coyoneda::<OptionBrand, _>::lift(Some(x + 1))
});
assert_eq!(result.lower(), Some(7)); }
#[test]
fn from_coyoneda_to_explicit() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
let explicit: CoyonedaExplicit<VecBrand, i32, i32, fn(i32) -> i32> = coyo.into();
assert_eq!(explicit.lower(), vec![2, 3, 4]);
}
#[test]
fn from_coyoneda_then_map_lower() {
let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
let result = CoyonedaExplicit::<VecBrand, i32, i32, fn(i32) -> i32>::from(coyo)
.map(|x| x * 2)
.lower();
assert_eq!(result, vec![4, 6, 8]);
}
#[test]
fn from_roundtrip_preserves_semantics() {
let original = vec![1, 2, 3];
let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(original.clone()).map(|x| x + 1);
let coyo: Coyoneda<VecBrand, i32> = explicit.into();
let back: CoyonedaExplicit<VecBrand, i32, i32, fn(i32) -> i32> = coyo.into();
assert_eq!(back.lower(), vec![2, 3, 4]);
}
mod property {
use {
crate::{
brands::*,
functions::*,
types::*,
},
quickcheck_macros::quickcheck,
};
#[quickcheck]
fn functor_identity_vec(v: Vec<i32>) -> bool {
let coyo = Coyoneda::<VecBrand, _>::lift(v.clone());
explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(identity, coyo).lower() == v
}
#[quickcheck]
fn functor_identity_option(x: Option<i32>) -> bool {
let coyo = Coyoneda::<OptionBrand, _>::lift(x);
explicit::map::<CoyonedaBrand<OptionBrand>, _, _, _, _>(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 = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
compose(f, g),
Coyoneda::<VecBrand, _>::lift(v.clone()),
)
.lower();
let right = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
f,
explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
g,
Coyoneda::<VecBrand, _>::lift(v),
),
)
.lower();
left == right
}
}
}