Skip to main content

fp_library/types/
coyoneda.rs

1//! The free functor, providing a [`Functor`](crate::classes::Functor) instance for any
2//! type constructor with the appropriate [`Kind`](crate::kinds) signature.
3//!
4//! `Coyoneda F` is a `Functor` even when `F` itself is not. The `Functor` bound on `F`
5//! is only required when calling [`lower`](Coyoneda::lower) to extract the underlying
6//! value. This is the defining property of the free functor construction.
7//!
8//! ## Performance characteristics
9//!
10//! Each call to [`map`](Coyoneda::map) wraps the previous value in a new layer that
11//! stores the mapping function inline (one heap allocation for the layer itself; the
12//! function is not separately boxed). At [`lower`](Coyoneda::lower) time, each layer
13//! applies its function via `F::map`. For k chained maps, `lower` makes k calls to
14//! `F::map`.
15//!
16//! This is a consequence of Rust's dyn-compatibility rules: composing functions across
17//! an existential boundary requires generic methods on trait objects, which Rust does not
18//! support. For true single-pass fusion on eager brands like
19//! [`VecBrand`](crate::brands::VecBrand), compose functions before mapping:
20//!
21//! ```
22//! use fp_library::{
23//! 	brands::*,
24//! 	functions::*,
25//! };
26//!
27//! // Single traversal: compose first, then map once.
28//! let result = explicit::map::<VecBrand, _, _, _, _>(
29//! 	compose(|x: i32| x.to_string(), compose(|x| x * 2, |x: i32| x + 1)),
30//! 	vec![1, 2, 3],
31//! );
32//! assert_eq!(result, vec!["4", "6", "8"]);
33//! ```
34//!
35//! For single-pass fusion that applies one `F::map` regardless of chain depth,
36//! see [`CoyonedaExplicit`](crate::types::CoyonedaExplicit).
37//!
38//! ## Stack safety
39//!
40//! Each chained [`map`](Coyoneda::map) adds a layer of recursion to
41//! [`lower`](Coyoneda::lower). Deep chains (thousands of maps) can overflow the stack.
42//! Three mitigations are available:
43//!
44//! 1. **`stacker` feature (automatic).** Enable the `stacker` feature flag to use
45//!    adaptive stack growth in `lower`. This is transparent and handles arbitrarily
46//!    deep chains with near-zero overhead when the stack is sufficient.
47//! 2. **[`collapse`](Coyoneda::collapse) (manual).** Call `collapse()` periodically
48//!    to flatten accumulated layers. Requires `F: Functor`.
49//! 3. **[`CoyonedaExplicit`](crate::types::CoyonedaExplicit) with `.boxed()`.** An
50//!    alternative type that composes functions at the type level, producing a single
51//!    `F::map` call at lower time regardless of chain depth.
52//!
53//! ## Limitations
54//!
55//! All limitations stem from a single root cause: Rust trait objects cannot have methods
56//! with generic type parameters (the vtable is fixed at compile time). This prevents
57//! "opening" the existential type `B` hidden inside each layer.
58//!
59//! - **No map fusion.** PureScript's Coyoneda composes `f <<< k` eagerly so that `lower`
60//!   calls `F::map` exactly once regardless of how many maps were chained. This Rust
61//!   implementation cannot compose functions across the trait-object boundary because the
62//!   required `map_inner<C>` method is generic over `C`. Each layer calls `F::map`
63//!   independently, so k chained maps produce k calls to `F::map` at `lower` time, the
64//!   same cost as chaining `F::map` directly.
65//!
66//! - **[`Foldable`](crate::classes::Foldable) requires `F: Functor`.** PureScript's
67//!   `Foldable` for `Coyoneda` only needs `Foldable f` because `unCoyoneda` opens the
68//!   existential to compose the fold function with the accumulated mapping function,
69//!   folding the original `F B` in a single pass. This implementation cannot add a
70//!   `fold_map_inner` method to the inner trait because it is generic over the monoid type
71//!   `M`, breaking dyn-compatibility. Instead, it lowers first (requiring `F: Functor`),
72//!   then folds.
73//!
74//! - **[`hoist`](Coyoneda::hoist) requires `F: Functor`.** PureScript's `hoistCoyoneda`
75//!   applies the natural transformation directly to the hidden `F B` via `unCoyoneda`. A
76//!   `hoist_inner<G>` method would be generic over the target brand `G`, so this
77//!   implementation lowers first, transforms, then re-lifts.
78//!
79//! - **No `unCoyoneda`.** PureScript's rank-2 eliminator
80//!   `(forall b. (b -> a) -> f b -> r) -> Coyoneda f a -> r` has no Rust equivalent
81//!   because closures cannot be polymorphic over type parameters. Operation-specific
82//!   methods on the inner trait are used instead.
83//!
84//! - **Not `Clone`.** The inner trait object `Box<dyn CoyonedaInner>` is not `Clone`.
85//!   This prevents implementing [`Traversable`](crate::classes::Traversable) (which
86//!   requires `Self::Of<'a, B>: Clone`) and
87//!   [`Semiapplicative`](crate::classes::Semiapplicative). An `Rc`/`Arc`-wrapped variant
88//!   would address this.
89//!
90//! - **Missing type class instances.** PureScript provides `Traversable`, `Extend`,
91//!   `Comonad`, `Eq`, `Ord`, and others. This implementation currently provides
92//!   [`Functor`](crate::classes::Functor), [`Pointed`](crate::classes::Pointed),
93//!   [`Foldable`](crate::classes::Foldable), [`Lift`](crate::classes::Lift),
94//!   [`Semiapplicative`](crate::classes::Semiapplicative),
95//!   [`Semimonad`](crate::classes::Semimonad), and [`Monad`](crate::classes::Monad)
96//!   (via blanket impl).
97//!
98//! ## Comparison with PureScript
99//!
100//! This implementation is based on PureScript's
101//! [`Data.Coyoneda`](https://github.com/purescript/purescript-free/blob/master/src/Data/Coyoneda.purs).
102//! PureScript uses `Exists` for existential quantification and composes functions eagerly
103//! (single `F::map` at `lower` time regardless of how many maps were chained). Rust uses
104//! layered trait objects because dyn-compatibility prevents the generic `map_inner<C>`
105//! method needed for eager composition.
106//!
107//! The PureScript API also provides `unCoyoneda` (a rank-2 eliminator), `coyoneda`
108//! (a general constructor), and `hoistCoyoneda` (natural transformation). This
109//! implementation provides [`Coyoneda::new`] (equivalent to `coyoneda`) and
110//! [`Coyoneda::hoist`] (equivalent to `hoistCoyoneda`, but requires `F: Functor`).
111//! A direct equivalent of `unCoyoneda` is not possible in Rust without rank-2 types.
112//!
113//! ### Examples
114//!
115//! ```
116//! use fp_library::{
117//! 	brands::*,
118//! 	functions::*,
119//! 	types::*,
120//! };
121//!
122//! let v = vec![1, 2, 3];
123//!
124//! // Lift into Coyoneda, chain maps, then lower.
125//! let result = Coyoneda::<VecBrand, _>::lift(v)
126//! 	.map(|x| x + 1)
127//! 	.map(|x| x * 2)
128//! 	.map(|x| x.to_string())
129//! 	.lower();
130//!
131//! assert_eq!(result, vec!["4", "6", "8"]);
132//! ```
133
134#[fp_macros::document_module]
135mod inner {
136	use {
137		crate::{
138			Apply,
139			brands::CoyonedaBrand,
140			classes::*,
141			impl_kind,
142			kinds::*,
143			types::CoyonedaExplicit,
144		},
145		fp_macros::*,
146	};
147
148	// -- Inner trait (existential witness) --
149
150	/// Trait for lowering a `Coyoneda` value back to its underlying functor.
151	///
152	/// Each implementor stores a value of type `F B` for some hidden type `B`,
153	/// along with any accumulated mapping functions needed to produce `F A`.
154	#[document_type_parameters(
155		"The lifetime of the values.",
156		"The brand of the underlying type constructor.",
157		"The output type of the accumulated mapping function."
158	)]
159	#[document_parameters("The boxed trait object to consume.")]
160	trait CoyonedaInner<'a, F, A: 'a>: 'a
161	where
162		F: Kind_cdc7cd43dac7585f + 'a, {
163		/// Lower to the concrete functor by applying accumulated functions via `F::map`.
164		#[document_signature]
165		///
166		#[document_returns("The underlying functor value with accumulated functions applied.")]
167		#[document_examples]
168		///
169		/// ```
170		/// use fp_library::{
171		/// 	brands::*,
172		/// 	types::*,
173		/// };
174		///
175		/// let coyo = Coyoneda::<OptionBrand, _>::lift(Some(42));
176		/// assert_eq!(coyo.lower(), Some(42));
177		/// ```
178		fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
179		where
180			F: Functor;
181	}
182
183	// -- Base layer: wraps F<A> directly (identity mapping) --
184
185	/// Base layer created by [`Coyoneda::lift`]. Wraps `F A` with no mapping.
186	struct CoyonedaBase<'a, F, A: 'a>
187	where
188		F: Kind_cdc7cd43dac7585f + 'a, {
189		fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
190	}
191
192	#[document_type_parameters(
193		"The lifetime of the values.",
194		"The brand of the underlying type constructor.",
195		"The type of the value inside the functor."
196	)]
197	#[document_parameters("The base layer instance.")]
198	impl<'a, F, A: 'a> CoyonedaInner<'a, F, A> for CoyonedaBase<'a, F, A>
199	where
200		F: Kind_cdc7cd43dac7585f + 'a,
201	{
202		/// Returns the wrapped value directly without calling `F::map`.
203		#[document_signature]
204		///
205		#[document_returns("The underlying functor value, unchanged.")]
206		#[document_examples]
207		///
208		/// ```
209		/// use fp_library::{
210		/// 	brands::*,
211		/// 	types::*,
212		/// };
213		///
214		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
215		/// assert_eq!(coyo.lower(), vec![1, 2, 3]);
216		/// ```
217		fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
218		where
219			F: Functor, {
220			self.fa
221		}
222	}
223
224	// -- Map layer: wraps an inner Coyoneda and adds a mapping function --
225
226	/// Map layer created by [`Coyoneda::map`]. Stores the inner value and a function
227	/// to apply on top of it at [`lower`](Coyoneda::lower) time.
228	///
229	/// The function type `Func` is stored inline rather than boxed, eliminating one
230	/// heap allocation per [`map`](Coyoneda::map) call. The `Func` parameter is erased
231	/// by the outer `Box<dyn CoyonedaInner>` and does not appear in the public API.
232	struct CoyonedaMapLayer<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a>
233	where
234		F: Kind_cdc7cd43dac7585f + 'a, {
235		inner: Box<dyn CoyonedaInner<'a, F, B> + 'a>,
236		func: Func,
237	}
238
239	#[document_type_parameters(
240		"The lifetime of the values.",
241		"The brand of the underlying type constructor.",
242		"The input type of this layer's mapping function.",
243		"The output type of this layer's mapping function.",
244		"The type of this layer's mapping function."
245	)]
246	#[document_parameters("The map layer instance.")]
247	impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaInner<'a, F, A>
248		for CoyonedaMapLayer<'a, F, B, A, Func>
249	where
250		F: Kind_cdc7cd43dac7585f + 'a,
251	{
252		/// Lowers the inner value, then applies this layer's function via `F::map`.
253		#[document_signature]
254		///
255		#[document_returns("The underlying functor value with this layer's function applied.")]
256		#[document_examples]
257		///
258		/// ```
259		/// use fp_library::{
260		/// 	brands::*,
261		/// 	types::*,
262		/// };
263		///
264		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
265		/// assert_eq!(coyo.lower(), vec![2, 3, 4]);
266		/// ```
267		fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
268		where
269			F: Functor, {
270			#[cfg(feature = "stacker")]
271			{
272				stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
273					let lowered = self.inner.lower();
274					F::map(self.func, lowered)
275				})
276			}
277			#[cfg(not(feature = "stacker"))]
278			{
279				let lowered = self.inner.lower();
280				F::map(self.func, lowered)
281			}
282		}
283	}
284
285	// -- New layer: wraps F<B> and a function B -> A directly (used by Coyoneda::new) --
286
287	/// Layer created by [`Coyoneda::new`]. Stores the functor value and mapping
288	/// function directly, avoiding the extra box that wrapping a [`CoyonedaBase`]
289	/// inside a [`CoyonedaMapLayer`] would require.
290	///
291	/// The function type `Func` is stored inline rather than boxed, eliminating one
292	/// heap allocation per [`new`](Coyoneda::new) call. The `Func` parameter is erased
293	/// by the outer `Box<dyn CoyonedaInner>` and does not appear in the public API.
294	struct CoyonedaNewLayer<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a>
295	where
296		F: Kind_cdc7cd43dac7585f + 'a, {
297		fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
298		func: Func,
299	}
300
301	#[document_type_parameters(
302		"The lifetime of the values.",
303		"The brand of the underlying type constructor.",
304		"The type of the values in the underlying functor.",
305		"The output type of the mapping function.",
306		"The type of the mapping function."
307	)]
308	#[document_parameters("The new layer instance.")]
309	impl<'a, F, B: 'a, A: 'a, Func: Fn(B) -> A + 'a> CoyonedaInner<'a, F, A>
310		for CoyonedaNewLayer<'a, F, B, A, Func>
311	where
312		F: Kind_cdc7cd43dac7585f + 'a,
313	{
314		/// Applies the stored function to the stored functor value via `F::map`.
315		#[document_signature]
316		///
317		#[document_returns("The underlying functor value with the function applied.")]
318		#[document_examples]
319		///
320		/// ```
321		/// use fp_library::{
322		/// 	brands::*,
323		/// 	types::*,
324		/// };
325		///
326		/// let coyo = Coyoneda::<VecBrand, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
327		/// assert_eq!(coyo.lower(), vec![2, 4, 6]);
328		/// ```
329		fn lower(self: Box<Self>) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
330		where
331			F: Functor, {
332			F::map(self.func, self.fb)
333		}
334	}
335
336	// -- Outer type --
337
338	/// The free functor over a type constructor `F`.
339	///
340	/// `Coyoneda` provides a [`Functor`] instance for any type constructor `F` with the
341	/// appropriate [`Kind`](crate::kinds) signature, even if `F` itself does not implement
342	/// [`Functor`]. The `Functor` constraint is only needed when calling [`lower`](Coyoneda::lower).
343	///
344	/// This type is not `Clone`, `Send`, or `Sync`. It wraps a `Box<dyn CoyonedaInner>`,
345	/// so each value is single-owner and consumed by [`lower`](Coyoneda::lower).
346	///
347	/// See the [module documentation](crate::types::coyoneda) for limitations and performance notes.
348	#[document_type_parameters(
349		"The lifetime of the values.",
350		"The brand of the underlying type constructor.",
351		"The current output type."
352	)]
353	pub struct Coyoneda<'a, F, A: 'a>(Box<dyn CoyonedaInner<'a, F, A> + 'a>)
354	where
355		F: Kind_cdc7cd43dac7585f + 'a;
356
357	#[document_type_parameters(
358		"The lifetime of the values.",
359		"The brand of the underlying type constructor.",
360		"The current output type."
361	)]
362	#[document_parameters("The `Coyoneda` instance.")]
363	impl<'a, F, A: 'a> Coyoneda<'a, F, A>
364	where
365		F: Kind_cdc7cd43dac7585f + 'a,
366	{
367		/// Construct a `Coyoneda` from a function and a functor value.
368		///
369		/// This is the general constructor corresponding to PureScript's `coyoneda`.
370		/// It stores `fb` alongside `f` as a single deferred mapping step.
371		/// [`lift`](Coyoneda::lift) is equivalent to `new(|a| a, fa)`.
372		#[document_signature]
373		///
374		#[document_type_parameters("The type of the values in the underlying functor.")]
375		///
376		#[document_parameters("The function to defer.", "The functor value.")]
377		///
378		#[document_returns("A `Coyoneda` wrapping the value with the deferred function.")]
379		#[document_examples]
380		///
381		/// ```
382		/// use fp_library::{
383		/// 	brands::*,
384		/// 	types::*,
385		/// };
386		///
387		/// let coyo = Coyoneda::<VecBrand, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
388		/// assert_eq!(coyo.lower(), vec![2, 4, 6]);
389		/// ```
390		pub fn new<B: 'a>(
391			f: impl Fn(B) -> A + 'a,
392			fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
393		) -> Self {
394			Coyoneda(Box::new(CoyonedaNewLayer {
395				fb,
396				func: f,
397			}))
398		}
399
400		/// Lift a value of `F A` into `Coyoneda F A`.
401		///
402		/// This wraps the value directly with no mapping. O(1).
403		/// Equivalent to `Coyoneda::new(|a| a, fa)`.
404		#[document_signature]
405		///
406		#[document_parameters("The functor value to lift.")]
407		///
408		#[document_returns("A `Coyoneda` wrapping the value.")]
409		#[document_examples]
410		///
411		/// ```
412		/// use fp_library::{
413		/// 	brands::*,
414		/// 	types::*,
415		/// };
416		///
417		/// let coyo = Coyoneda::<OptionBrand, _>::lift(Some(42));
418		/// assert_eq!(coyo.lower(), Some(42));
419		/// ```
420		pub fn lift(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> Self {
421			Coyoneda(Box::new(CoyonedaBase {
422				fa,
423			}))
424		}
425
426		/// Lower the `Coyoneda` back to the underlying functor `F`.
427		///
428		/// Applies accumulated mapping functions via `F::map`. Requires `F: Functor`.
429		/// For k chained maps, this makes k calls to `F::map` (one per layer).
430		/// See the [module-level performance notes](crate::types::coyoneda#performance-characteristics) for details.
431		#[document_signature]
432		///
433		#[document_returns("The underlying functor value with all accumulated functions applied.")]
434		#[document_examples]
435		///
436		/// ```
437		/// use fp_library::{
438		/// 	brands::*,
439		/// 	functions::*,
440		/// 	types::*,
441		/// };
442		///
443		/// let result = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).lower();
444		///
445		/// assert_eq!(result, vec![2, 3, 4]);
446		/// ```
447		pub fn lower(self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
448		where
449			F: Functor, {
450			self.0.lower()
451		}
452
453		/// Collapse accumulated mapping layers by lowering and re-lifting.
454		///
455		/// This applies all accumulated `map` layers via [`lower`](Coyoneda::lower),
456		/// producing a concrete `F A`, then wraps the result back in a single-layer
457		/// `Coyoneda`. Useful for bounding recursion depth in long chains:
458		/// insert `collapse()` periodically to prevent stack overflow without
459		/// switching to [`CoyonedaExplicit`](crate::types::CoyonedaExplicit).
460		///
461		/// Requires `F: Functor`. Cost: one full `lower` pass.
462		#[document_signature]
463		///
464		#[document_returns("A fresh `Coyoneda` with a single base layer.")]
465		#[document_examples]
466		///
467		/// ```
468		/// use fp_library::{
469		/// 	brands::*,
470		/// 	types::*,
471		/// };
472		///
473		/// let mut coyo = Coyoneda::<VecBrand, _>::lift(vec![0i64]);
474		/// for i in 0 .. 50 {
475		/// 	coyo = coyo.map(|x| x + 1);
476		/// 	if i % 25 == 24 {
477		/// 		coyo = coyo.collapse();
478		/// 	}
479		/// }
480		/// assert_eq!(coyo.lower(), vec![50i64]);
481		/// ```
482		pub fn collapse(self) -> Self
483		where
484			F: Functor, {
485			Coyoneda::lift(self.lower())
486		}
487
488		/// Map a function over the `Coyoneda` value.
489		///
490		/// This wraps the current value in a new layer that stores `f`. The function
491		/// is applied when [`lower`](Coyoneda::lower) is called. O(1).
492		#[document_signature]
493		///
494		#[document_type_parameters("The new output type after applying the function.")]
495		///
496		#[document_parameters("The function to apply.")]
497		///
498		#[document_returns("A new `Coyoneda` with the function stored for deferred application.")]
499		#[document_examples]
500		///
501		/// ```
502		/// use fp_library::{
503		/// 	brands::*,
504		/// 	types::*,
505		/// };
506		///
507		/// let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x * 2).map(|x| x + 1);
508		///
509		/// assert_eq!(coyo.lower(), Some(11));
510		/// ```
511		pub fn map<B: 'a>(
512			self,
513			f: impl Fn(A) -> B + 'a,
514		) -> Coyoneda<'a, F, B> {
515			Coyoneda(Box::new(CoyonedaMapLayer {
516				inner: self.0,
517				func: f,
518			}))
519		}
520
521		/// Apply a natural transformation to the underlying functor.
522		///
523		/// Transforms a `Coyoneda<F, A>` into a `Coyoneda<G, A>` by lowering to `F`,
524		/// applying the natural transformation `F ~> G`, then lifting back into
525		/// `Coyoneda<G, A>`. Requires `F: Functor` (for lowering).
526		///
527		/// Corresponds to PureScript's `hoistCoyoneda`. Note: the PureScript version
528		/// does not require `F: Functor` because it can open the existential directly
529		/// via `unCoyoneda`. In Rust, dyn-compatibility prevents this.
530		#[document_signature]
531		///
532		#[document_type_parameters("The brand of the target functor.")]
533		///
534		#[document_parameters("The natural transformation from `F` to `G`.")]
535		///
536		#[document_returns("A new `Coyoneda` over the target functor `G`.")]
537		#[document_examples]
538		///
539		/// ```
540		/// use fp_library::{
541		/// 	brands::*,
542		/// 	classes::*,
543		/// 	types::*,
544		/// };
545		///
546		/// struct VecToOption;
547		/// impl NaturalTransformation<VecBrand, OptionBrand> for VecToOption {
548		/// 	fn transform<'a, A: 'a>(
549		/// 		&self,
550		/// 		fa: Vec<A>,
551		/// 	) -> Option<A> {
552		/// 		fa.into_iter().next()
553		/// 	}
554		/// }
555		///
556		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![10, 20, 30]);
557		/// let hoisted: Coyoneda<OptionBrand, i32> = coyo.hoist(VecToOption);
558		/// assert_eq!(hoisted.lower(), Some(10));
559		/// ```
560		pub fn hoist<G: Kind_cdc7cd43dac7585f + 'a>(
561			self,
562			nat: impl NaturalTransformation<F, G>,
563		) -> Coyoneda<'a, G, A>
564		where
565			F: Functor, {
566			Coyoneda::lift(nat.transform(self.lower()))
567		}
568	}
569
570	// -- Brand --
571
572	impl_kind! {
573		impl<F: Kind_cdc7cd43dac7585f + 'static> for CoyonedaBrand<F> {
574			type Of<'a, A: 'a>: 'a = Coyoneda<'a, F, A>;
575		}
576	}
577
578	// -- Functor implementation --
579
580	#[document_type_parameters("The brand of the underlying type constructor.")]
581	impl<F: Kind_cdc7cd43dac7585f + 'static> Functor for CoyonedaBrand<F> {
582		/// Maps a function over the `Coyoneda` value by adding a new mapping layer.
583		///
584		/// Does not require `F: Functor`. The function is stored and applied at
585		/// [`lower`](Coyoneda::lower) time.
586		#[document_signature]
587		///
588		#[document_type_parameters(
589			"The lifetime of the values.",
590			"The type of the current output.",
591			"The type of the new output."
592		)]
593		///
594		#[document_parameters("The function to apply.", "The `Coyoneda` value.")]
595		///
596		#[document_returns("A new `Coyoneda` with the function stored for deferred application.")]
597		#[document_examples]
598		///
599		/// ```
600		/// use fp_library::{
601		/// 	brands::*,
602		/// 	functions::*,
603		/// 	types::*,
604		/// };
605		///
606		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
607		/// let mapped = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(|x| x * 10, coyo);
608		/// assert_eq!(mapped.lower(), vec![10, 20, 30]);
609		/// ```
610		fn map<'a, A: 'a, B: 'a>(
611			func: impl Fn(A) -> B + 'a,
612			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
613		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
614			fa.map(func)
615		}
616	}
617
618	// -- Pointed implementation --
619
620	#[document_type_parameters("The brand of the underlying pointed functor.")]
621	impl<F: Pointed + 'static> Pointed for CoyonedaBrand<F> {
622		/// Wraps a value in a `Coyoneda` context by delegating to `F::pure` and lifting.
623		#[document_signature]
624		///
625		#[document_type_parameters("The lifetime of the value.", "The type of the value to wrap.")]
626		///
627		#[document_parameters("The value to wrap.")]
628		///
629		#[document_returns("A `Coyoneda` containing the pure value.")]
630		#[document_examples]
631		///
632		/// ```
633		/// use fp_library::{
634		/// 	brands::*,
635		/// 	functions::*,
636		/// 	types::*,
637		/// };
638		///
639		/// let coyo: Coyoneda<OptionBrand, i32> = pure::<CoyonedaBrand<OptionBrand>, _>(42);
640		/// assert_eq!(coyo.lower(), Some(42));
641		/// ```
642		fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
643			Coyoneda::lift(F::pure(a))
644		}
645	}
646
647	// -- Foldable implementation --
648
649	#[document_type_parameters("The brand of the underlying foldable functor.")]
650	impl<F: Functor + Foldable + 'static> Foldable for CoyonedaBrand<F> {
651		/// Folds the `Coyoneda` by lowering to the underlying functor and delegating.
652		///
653		/// This first applies all accumulated mapping functions via [`lower`](Coyoneda::lower),
654		/// then folds the resulting `F A` using `F`'s [`Foldable`] instance.
655		///
656		/// Note: unlike PureScript's `Foldable` for `Coyoneda`, this requires `F: Functor`
657		/// because the layered trait-object encoding does not support composing fold functions
658		/// through the existential boundary (doing so would require generic methods on the
659		/// inner trait, which are not dyn-compatible).
660		#[document_signature]
661		///
662		#[document_type_parameters(
663			"The lifetime of the elements.",
664			"The brand of the cloneable function to use.",
665			"The type of the elements in the structure.",
666			"The type of the monoid."
667		)]
668		///
669		#[document_parameters(
670			"The function to map each element to a monoid.",
671			"The `Coyoneda` structure to fold."
672		)]
673		///
674		#[document_returns("The combined monoid value.")]
675		#[document_examples]
676		///
677		/// ```
678		/// use fp_library::{
679		/// 	brands::*,
680		/// 	functions::*,
681		/// 	types::*,
682		/// };
683		///
684		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
685		///
686		/// let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
687		/// 	|x: i32| x.to_string(),
688		/// 	coyo,
689		/// );
690		/// assert_eq!(result, "102030".to_string());
691		/// ```
692		fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
693			func: impl Fn(A) -> M + 'a,
694			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
695		) -> M
696		where
697			M: Monoid + 'a,
698			FnBrand: LiftFn + 'a, {
699			F::fold_map::<FnBrand, A, M>(func, fa.lower())
700		}
701	}
702
703	// -- Lift implementation --
704
705	#[document_type_parameters("The brand of the underlying type constructor.")]
706	impl<F: Functor + Lift + 'static> Lift for CoyonedaBrand<F> {
707		/// Lifts a binary function into the `Coyoneda` context by lowering both
708		/// arguments and delegating to `F::lift2`.
709		///
710		/// Requires `F: Functor` (for lowering) and `F: Lift`.
711		#[document_signature]
712		///
713		#[document_type_parameters(
714			"The lifetime of the values.",
715			"The type of the first value.",
716			"The type of the second value.",
717			"The type of the result."
718		)]
719		///
720		#[document_parameters(
721			"The binary function to apply.",
722			"The first `Coyoneda` value.",
723			"The second `Coyoneda` value."
724		)]
725		///
726		#[document_returns("A `Coyoneda` containing the result of applying the function.")]
727		#[document_examples]
728		///
729		/// ```
730		/// use fp_library::{
731		/// 	brands::*,
732		/// 	functions::*,
733		/// 	types::*,
734		/// };
735		///
736		/// let a = Coyoneda::<OptionBrand, _>::lift(Some(3));
737		/// let b = Coyoneda::<OptionBrand, _>::lift(Some(4));
738		/// let result =
739		/// 	explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
740		/// assert_eq!(result.lower(), Some(7));
741		/// ```
742		fn lift2<'a, A, B, C>(
743			func: impl Fn(A, B) -> C + 'a,
744			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
745			fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
746		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
747		where
748			A: Clone + 'a,
749			B: Clone + 'a,
750			C: 'a, {
751			Coyoneda::lift(F::lift2(func, fa.lower(), fb.lower()))
752		}
753	}
754
755	// -- ApplyFirst / ApplySecond --
756
757	#[document_type_parameters("The brand of the underlying type constructor.")]
758	impl<F: Functor + Lift + 'static> ApplyFirst for CoyonedaBrand<F> {}
759	#[document_type_parameters("The brand of the underlying type constructor.")]
760	impl<F: Functor + Lift + 'static> ApplySecond for CoyonedaBrand<F> {}
761
762	// -- Semiapplicative implementation --
763
764	#[document_type_parameters("The brand of the underlying type constructor.")]
765	impl<F: Functor + Semiapplicative + 'static> Semiapplicative for CoyonedaBrand<F> {
766		/// Applies a `Coyoneda`-wrapped function to a `Coyoneda`-wrapped value by
767		/// lowering both and delegating to `F::apply`.
768		///
769		/// Requires `F: Functor` (for lowering) and `F: Semiapplicative`.
770		#[document_signature]
771		///
772		#[document_type_parameters(
773			"The lifetime of the values.",
774			"The brand of the cloneable function wrapper.",
775			"The type of the input value.",
776			"The type of the output value."
777		)]
778		///
779		#[document_parameters(
780			"The `Coyoneda` containing the function(s).",
781			"The `Coyoneda` containing the value(s)."
782		)]
783		///
784		#[document_returns("A `Coyoneda` containing the result(s) of applying the function(s).")]
785		#[document_examples]
786		///
787		/// ```
788		/// use fp_library::{
789		/// 	brands::*,
790		/// 	classes::*,
791		/// 	functions::*,
792		/// 	types::*,
793		/// };
794		///
795		/// let ff = Coyoneda::<OptionBrand, _>::lift(Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2)));
796		/// let fa = Coyoneda::<OptionBrand, _>::lift(Some(5));
797		/// let result = apply::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _>(ff, fa);
798		/// assert_eq!(result.lower(), Some(10));
799		/// ```
800		fn apply<'a, FnBrand: 'a + CloneFn, A: 'a + Clone, B: 'a>(
801			ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneFn>::Of<'a, A, B>>),
802			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
803		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
804			Coyoneda::lift(F::apply::<FnBrand, A, B>(ff.lower(), fa.lower()))
805		}
806	}
807
808	// -- Semimonad implementation --
809
810	#[document_type_parameters("The brand of the underlying type constructor.")]
811	impl<F: Functor + Semimonad + 'static> Semimonad for CoyonedaBrand<F> {
812		/// Chains `Coyoneda` computations by lowering to the underlying functor,
813		/// binding via `F::bind`, then re-lifting the result.
814		///
815		/// Requires `F: Functor` (for lowering) and `F: Semimonad` (for binding).
816		#[document_signature]
817		///
818		#[document_type_parameters(
819			"The lifetime of the values.",
820			"The type of the value in the input `Coyoneda`.",
821			"The type of the value in the output `Coyoneda`."
822		)]
823		///
824		#[document_parameters(
825			"The input `Coyoneda` value.",
826			"The function to apply to the inner value, returning a new `Coyoneda`."
827		)]
828		///
829		#[document_returns("A new `Coyoneda` containing the bound result.")]
830		#[document_examples]
831		///
832		/// ```
833		/// use fp_library::{
834		/// 	brands::*,
835		/// 	functions::*,
836		/// 	types::*,
837		/// };
838		///
839		/// let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5));
840		/// let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
841		/// 	Coyoneda::<OptionBrand, _>::lift(Some(x * 2))
842		/// });
843		/// assert_eq!(result.lower(), Some(10));
844		/// ```
845		fn bind<'a, A: 'a, B: 'a>(
846			ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
847			func: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
848		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
849			Coyoneda::lift(F::bind(ma.lower(), move |a| func(a).lower()))
850		}
851	}
852
853	// -- From<Coyoneda> for CoyonedaExplicit --
854
855	#[document_type_parameters(
856		"The lifetime of the values.",
857		"The brand of the underlying foldable functor.",
858		"The type of the values."
859	)]
860	impl<'a, F, A: 'a> From<Coyoneda<'a, F, A>> for CoyonedaExplicit<'a, F, A, A, fn(A) -> A>
861	where
862		F: Kind_cdc7cd43dac7585f + Functor + 'a,
863	{
864		/// Convert a [`Coyoneda`] into a [`CoyonedaExplicit`] by lowering to the
865		/// underlying functor and re-lifting.
866		///
867		/// This calls [`lower()`](Coyoneda::lower), which applies all accumulated
868		/// mapping layers via `F::map`. For eager containers like `Vec`, this
869		/// allocates and traverses the full container. The cost is proportional to
870		/// the number of chained maps and the container size.
871		#[document_signature]
872		///
873		#[document_parameters("The `Coyoneda` to convert.")]
874		///
875		#[document_returns(
876			"A `CoyonedaExplicit` in identity position (B = A) wrapping the lowered value."
877		)]
878		#[document_examples]
879		///
880		/// ```
881		/// use fp_library::{
882		/// 	brands::*,
883		/// 	types::*,
884		/// };
885		///
886		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
887		/// let explicit: CoyonedaExplicit<VecBrand, i32, i32, fn(i32) -> i32> = coyo.into();
888		/// assert_eq!(explicit.lower(), vec![2, 3, 4]);
889		/// ```
890		fn from(coyo: Coyoneda<'a, F, A>) -> Self {
891			CoyonedaExplicit::lift(coyo.lower())
892		}
893	}
894
895	// -- Debug --
896
897	#[document_type_parameters(
898		"The lifetime of the values.",
899		"The brand of the underlying type constructor.",
900		"The current output type."
901	)]
902	#[document_parameters("The `Coyoneda` instance.")]
903	impl<'a, F, A: 'a> core::fmt::Debug for Coyoneda<'a, F, A>
904	where
905		F: Kind_cdc7cd43dac7585f + 'a,
906	{
907		/// Formats the `Coyoneda` as an opaque value.
908		///
909		/// The inner layers and functions cannot be inspected, so the output
910		/// is always `Coyoneda(<opaque>)`.
911		#[document_signature]
912		///
913		#[document_parameters("The formatter.")]
914		///
915		#[document_returns("The formatting result.")]
916		#[document_examples]
917		///
918		/// ```
919		/// use fp_library::{
920		/// 	brands::*,
921		/// 	types::*,
922		/// };
923		///
924		/// let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
925		/// assert_eq!(format!("{:?}", coyo), "Coyoneda(<opaque>)");
926		/// ```
927		fn fmt(
928			&self,
929			f: &mut core::fmt::Formatter<'_>,
930		) -> core::fmt::Result {
931			f.write_str("Coyoneda(<opaque>)")
932		}
933	}
934}
935
936pub use inner::*;
937
938#[cfg(test)]
939mod tests {
940	use crate::{
941		brands::*,
942		classes::*,
943		functions::*,
944		types::*,
945	};
946
947	#[test]
948	fn lift_lower_identity_option() {
949		let coyo = Coyoneda::<OptionBrand, _>::lift(Some(42));
950		assert_eq!(coyo.lower(), Some(42));
951	}
952
953	#[test]
954	fn lift_lower_identity_none() {
955		let coyo = Coyoneda::<OptionBrand, i32>::lift(None);
956		assert_eq!(coyo.lower(), None);
957	}
958
959	#[test]
960	fn lift_lower_identity_vec() {
961		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
962		assert_eq!(coyo.lower(), vec![1, 2, 3]);
963	}
964
965	#[test]
966	fn new_constructor() {
967		let coyo = Coyoneda::<VecBrand, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
968		assert_eq!(coyo.lower(), vec![2, 4, 6]);
969	}
970
971	#[test]
972	fn new_is_equivalent_to_lift_then_map() {
973		let f = |x: i32| x.to_string();
974		let v = vec![1, 2, 3];
975
976		let via_new = Coyoneda::<VecBrand, _>::new(f, v.clone()).lower();
977		let via_lift_map = Coyoneda::<VecBrand, _>::lift(v).map(f).lower();
978
979		assert_eq!(via_new, via_lift_map);
980	}
981
982	#[test]
983	fn single_map_option() {
984		let result = Coyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x * 2).lower();
985		assert_eq!(result, Some(10));
986	}
987
988	#[test]
989	fn chained_maps_vec() {
990		let result = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3])
991			.map(|x| x + 1)
992			.map(|x| x * 2)
993			.map(|x| x.to_string())
994			.lower();
995		assert_eq!(result, vec!["4", "6", "8"]);
996	}
997
998	#[test]
999	fn functor_identity_law() {
1000		// map(identity, fa) = fa
1001		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
1002		let result = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(identity, coyo).lower();
1003		assert_eq!(result, vec![1, 2, 3]);
1004	}
1005
1006	#[test]
1007	fn functor_composition_law() {
1008		// map(compose(f, g), fa) = map(f, map(g, fa))
1009		let f = |x: i32| x + 1;
1010		let g = |x: i32| x * 2;
1011
1012		let coyo1 = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
1013		let left =
1014			explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(compose(f, g), coyo1).lower();
1015
1016		let coyo2 = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
1017		let right = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
1018			f,
1019			explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(g, coyo2),
1020		)
1021		.lower();
1022
1023		assert_eq!(left, right);
1024	}
1025
1026	#[test]
1027	fn many_chained_maps() {
1028		let mut coyo = Coyoneda::<VecBrand, _>::lift(vec![0i64]);
1029		for _ in 0 .. 100 {
1030			coyo = coyo.map(|x| x + 1);
1031		}
1032		assert_eq!(coyo.lower(), vec![100i64]);
1033	}
1034
1035	// -- Collapse tests --
1036
1037	#[test]
1038	fn collapse_preserves_value() {
1039		let coyo =
1040			Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).map(|x| x * 2).collapse();
1041		assert_eq!(coyo.lower(), vec![4, 6, 8]);
1042	}
1043
1044	#[test]
1045	fn collapse_then_map() {
1046		let coyo =
1047			Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).collapse().map(|x| x * 2);
1048		assert_eq!(coyo.lower(), vec![4, 6, 8]);
1049	}
1050
1051	#[test]
1052	fn collapse_periodic_in_loop() {
1053		let mut coyo = Coyoneda::<VecBrand, _>::lift(vec![0i64]);
1054		for i in 0 .. 50 {
1055			coyo = coyo.map(|x| x + 1);
1056			if i % 25 == 24 {
1057				coyo = coyo.collapse();
1058			}
1059		}
1060		assert_eq!(coyo.lower(), vec![50i64]);
1061	}
1062
1063	#[test]
1064	fn map_on_none_stays_none() {
1065		let result =
1066			Coyoneda::<OptionBrand, _>::lift(None::<i32>).map(|x| x + 1).map(|x| x * 2).lower();
1067		assert_eq!(result, None);
1068	}
1069
1070	#[test]
1071	fn map_free_function_dispatches_to_brand() {
1072		let coyo = Coyoneda::<OptionBrand, _>::lift(Some(10));
1073		let result: Coyoneda<OptionBrand, String> =
1074			explicit::map::<CoyonedaBrand<OptionBrand>, _, _, _, _>(|x: i32| x.to_string(), coyo);
1075		assert_eq!(result.lower(), Some("10".to_string()));
1076	}
1077
1078	#[test]
1079	fn lift_lower_roundtrip_preserves_value() {
1080		let original = vec![10, 20, 30];
1081		let roundtrip = Coyoneda::<VecBrand, _>::lift(original.clone()).lower();
1082		assert_eq!(roundtrip, original);
1083	}
1084
1085	// -- Pointed tests --
1086
1087	#[test]
1088	fn pointed_pure_option() {
1089		let coyo: Coyoneda<OptionBrand, i32> = pure::<CoyonedaBrand<OptionBrand>, _>(42);
1090		assert_eq!(coyo.lower(), Some(42));
1091	}
1092
1093	#[test]
1094	fn pointed_pure_vec() {
1095		let coyo: Coyoneda<VecBrand, i32> = pure::<CoyonedaBrand<VecBrand>, _>(42);
1096		assert_eq!(coyo.lower(), vec![42]);
1097	}
1098
1099	// -- Hoist tests --
1100
1101	struct VecToOption;
1102	impl NaturalTransformation<VecBrand, OptionBrand> for VecToOption {
1103		fn transform<'a, A: 'a>(
1104			&self,
1105			fa: Vec<A>,
1106		) -> Option<A> {
1107			fa.into_iter().next()
1108		}
1109	}
1110
1111	#[test]
1112	fn hoist_vec_to_option() {
1113		let coyo = Coyoneda::<VecBrand, _>::lift(vec![10, 20, 30]);
1114		let hoisted: Coyoneda<OptionBrand, i32> = coyo.hoist(VecToOption);
1115		assert_eq!(hoisted.lower(), Some(10));
1116	}
1117
1118	#[test]
1119	fn hoist_with_maps() {
1120		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
1121		let hoisted = coyo.hoist(VecToOption);
1122		assert_eq!(hoisted.lower(), Some(10));
1123	}
1124
1125	#[test]
1126	fn hoist_then_map() {
1127		let coyo = Coyoneda::<VecBrand, _>::lift(vec![5, 10, 15]);
1128		let hoisted = coyo.hoist(VecToOption).map(|x: i32| x.to_string());
1129		assert_eq!(hoisted.lower(), Some("5".to_string()));
1130	}
1131
1132	// -- Foldable tests --
1133
1134	#[test]
1135	fn fold_map_on_lifted_vec() {
1136		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
1137		let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
1138			|x: i32| x.to_string(),
1139			coyo,
1140		);
1141		assert_eq!(result, "123".to_string());
1142	}
1143
1144	#[test]
1145	fn fold_map_on_mapped_coyoneda() {
1146		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
1147		let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
1148			|x: i32| x.to_string(),
1149			coyo,
1150		);
1151		assert_eq!(result, "102030".to_string());
1152	}
1153
1154	#[test]
1155	fn fold_right_on_coyoneda() {
1156		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 2);
1157		let result = explicit::fold_right::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _, _, _>(
1158			|a: i32, b: i32| a + b,
1159			0,
1160			coyo,
1161		);
1162		assert_eq!(result, 12); // (1*2) + (2*2) + (3*2) = 2 + 4 + 6 = 12
1163	}
1164
1165	#[test]
1166	fn fold_left_on_coyoneda() {
1167		let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x + 1);
1168		let result = explicit::fold_left::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _, _, _>(
1169			|acc: i32, a: i32| acc + a,
1170			10,
1171			coyo,
1172		);
1173		assert_eq!(result, 16); // 10 + (5 + 1) = 16
1174	}
1175
1176	#[test]
1177	fn fold_map_on_none_is_empty() {
1178		let coyo = Coyoneda::<OptionBrand, i32>::lift(None).map(|x| x + 1);
1179		let result = explicit::fold_map::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _, _, _>(
1180			|x: i32| x.to_string(),
1181			coyo,
1182		);
1183		assert_eq!(result, String::new());
1184	}
1185
1186	// -- Lift tests --
1187
1188	#[test]
1189	fn lift2_option_both_some() {
1190		let a = Coyoneda::<OptionBrand, _>::lift(Some(3));
1191		let b = Coyoneda::<OptionBrand, _>::lift(Some(4));
1192		let result =
1193			explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
1194		assert_eq!(result.lower(), Some(7));
1195	}
1196
1197	#[test]
1198	fn lift2_option_one_none() {
1199		let a = Coyoneda::<OptionBrand, _>::lift(Some(3));
1200		let b = Coyoneda::<OptionBrand, i32>::lift(None);
1201		let result =
1202			explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
1203		assert_eq!(result.lower(), None);
1204	}
1205
1206	#[test]
1207	fn lift2_vec() {
1208		let a = Coyoneda::<VecBrand, _>::lift(vec![1, 2]);
1209		let b = Coyoneda::<VecBrand, _>::lift(vec![10, 20]);
1210		let result =
1211			explicit::lift2::<CoyonedaBrand<VecBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
1212		assert_eq!(result.lower(), vec![11, 21, 12, 22]);
1213	}
1214
1215	#[test]
1216	fn lift2_with_prior_maps() {
1217		let a = Coyoneda::<OptionBrand, _>::lift(Some(3)).map(|x| x * 2);
1218		let b = Coyoneda::<OptionBrand, _>::lift(Some(4)).map(|x| x + 1);
1219		let result =
1220			explicit::lift2::<CoyonedaBrand<OptionBrand>, _, _, _, _, _, _>(|x, y| x + y, a, b);
1221		assert_eq!(result.lower(), Some(11)); // (3*2) + (4+1)
1222	}
1223
1224	// -- Semiapplicative tests --
1225
1226	#[test]
1227	fn apply_option_some() {
1228		let ff =
1229			Coyoneda::<OptionBrand, _>::lift(Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2)));
1230		let fa = Coyoneda::<OptionBrand, _>::lift(Some(5));
1231		let result = apply::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _>(ff, fa);
1232		assert_eq!(result.lower(), Some(10));
1233	}
1234
1235	#[test]
1236	fn apply_option_none_fn() {
1237		let ff = Coyoneda::<OptionBrand, _>::lift(None::<<RcFnBrand as CloneFn>::Of<'_, i32, i32>>);
1238		let fa = Coyoneda::<OptionBrand, _>::lift(Some(5));
1239		let result = apply::<RcFnBrand, CoyonedaBrand<OptionBrand>, _, _>(ff, fa);
1240		assert_eq!(result.lower(), None);
1241	}
1242
1243	#[test]
1244	fn apply_vec() {
1245		let ff = Coyoneda::<VecBrand, _>::lift(vec![
1246			lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1),
1247			lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 10),
1248		]);
1249		let fa = Coyoneda::<VecBrand, _>::lift(vec![2i32, 3]);
1250		let result = apply::<RcFnBrand, CoyonedaBrand<VecBrand>, _, _>(ff, fa);
1251		assert_eq!(result.lower(), vec![3, 4, 20, 30]);
1252	}
1253
1254	// -- Semimonad tests --
1255
1256	#[test]
1257	fn bind_option_some() {
1258		let coyo = Coyoneda::<OptionBrand, _>::lift(Some(5));
1259		let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
1260			Coyoneda::<OptionBrand, _>::lift(Some(x * 2))
1261		});
1262		assert_eq!(result.lower(), Some(10));
1263	}
1264
1265	#[test]
1266	fn bind_option_none() {
1267		let coyo = Coyoneda::<OptionBrand, i32>::lift(None);
1268		let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
1269			Coyoneda::<OptionBrand, _>::lift(Some(x * 2))
1270		});
1271		assert_eq!(result.lower(), None);
1272	}
1273
1274	#[test]
1275	fn bind_vec() {
1276		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1i32, 2, 3]);
1277		let result = explicit::bind::<CoyonedaBrand<VecBrand>, _, _, _, _>(coyo, |x| {
1278			Coyoneda::<VecBrand, _>::lift(vec![x, x * 10])
1279		});
1280		assert_eq!(result.lower(), vec![1, 10, 2, 20, 3, 30]);
1281	}
1282
1283	#[test]
1284	fn bind_after_map() {
1285		let coyo = Coyoneda::<OptionBrand, _>::lift(Some(3)).map(|x| x * 2);
1286		let result = explicit::bind::<CoyonedaBrand<OptionBrand>, _, _, _, _>(coyo, |x| {
1287			Coyoneda::<OptionBrand, _>::lift(Some(x + 1))
1288		});
1289		assert_eq!(result.lower(), Some(7)); // (3 * 2) + 1
1290	}
1291
1292	// -- From conversion tests --
1293
1294	#[test]
1295	fn from_coyoneda_to_explicit() {
1296		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
1297		let explicit: CoyonedaExplicit<VecBrand, i32, i32, fn(i32) -> i32> = coyo.into();
1298		assert_eq!(explicit.lower(), vec![2, 3, 4]);
1299	}
1300
1301	#[test]
1302	fn from_coyoneda_then_map_lower() {
1303		let coyo = Coyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
1304		let result = CoyonedaExplicit::<VecBrand, i32, i32, fn(i32) -> i32>::from(coyo)
1305			.map(|x| x * 2)
1306			.lower();
1307		assert_eq!(result, vec![4, 6, 8]);
1308	}
1309
1310	#[test]
1311	fn from_roundtrip_preserves_semantics() {
1312		let original = vec![1, 2, 3];
1313
1314		// CoyonedaExplicit -> Coyoneda -> CoyonedaExplicit
1315		let explicit = CoyonedaExplicit::<VecBrand, _, _, _>::lift(original.clone()).map(|x| x + 1);
1316		let coyo: Coyoneda<VecBrand, i32> = explicit.into();
1317		let back: CoyonedaExplicit<VecBrand, i32, i32, fn(i32) -> i32> = coyo.into();
1318		assert_eq!(back.lower(), vec![2, 3, 4]);
1319	}
1320
1321	// -- Property-based tests --
1322
1323	mod property {
1324		use {
1325			crate::{
1326				brands::*,
1327				functions::*,
1328				types::*,
1329			},
1330			quickcheck_macros::quickcheck,
1331		};
1332
1333		#[quickcheck]
1334		fn functor_identity_vec(v: Vec<i32>) -> bool {
1335			let coyo = Coyoneda::<VecBrand, _>::lift(v.clone());
1336			explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(identity, coyo).lower() == v
1337		}
1338
1339		#[quickcheck]
1340		fn functor_identity_option(x: Option<i32>) -> bool {
1341			let coyo = Coyoneda::<OptionBrand, _>::lift(x);
1342			explicit::map::<CoyonedaBrand<OptionBrand>, _, _, _, _>(identity, coyo).lower() == x
1343		}
1344
1345		#[quickcheck]
1346		fn functor_composition_vec(v: Vec<i32>) -> bool {
1347			let f = |x: i32| x.wrapping_add(1);
1348			let g = |x: i32| x.wrapping_mul(2);
1349
1350			let left = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
1351				compose(f, g),
1352				Coyoneda::<VecBrand, _>::lift(v.clone()),
1353			)
1354			.lower();
1355
1356			let right = explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
1357				f,
1358				explicit::map::<CoyonedaBrand<VecBrand>, _, _, _, _>(
1359					g,
1360					Coyoneda::<VecBrand, _>::lift(v),
1361				),
1362			)
1363			.lower();
1364
1365			left == right
1366		}
1367	}
1368}