Skip to main content

fp_library/types/
arc_coyoneda.rs

1//! Thread-safe reference-counted free functor with `Clone` support.
2//!
3//! [`ArcCoyoneda`] is a variant of [`Coyoneda`](crate::types::Coyoneda) that wraps
4//! its inner layers in [`Arc`](std::sync::Arc) instead of [`Box`], making it `Clone`,
5//! `Send`, and `Sync`. This enables sharing deferred map chains across threads.
6//!
7//! ## Trade-offs vs `RcCoyoneda`
8//!
9//! - **Thread safety:** `ArcCoyoneda` is `Send + Sync`. [`RcCoyoneda`](crate::types::RcCoyoneda)
10//!   is not.
11//! - **Overhead:** Atomic reference counting is slightly more expensive than
12//!   non-atomic (`Rc`). Use `RcCoyoneda` when thread safety is not needed.
13//! - **Allocation:** Each [`map`](ArcCoyoneda::map) allocates 2 `Arc` values (one for
14//!   the layer, one for the function). Same as `RcCoyoneda`.
15//!
16//! ## Stack safety
17//!
18//! Each chained [`map`](ArcCoyoneda::map) adds a layer of recursion to
19//! [`lower_ref`](ArcCoyoneda::lower_ref). Deep chains (thousands of maps) can overflow the stack.
20//! Three mitigations are available:
21//!
22//! 1. **`stacker` feature (automatic).** Enable the `stacker` feature flag to use
23//!    adaptive stack growth in `lower_ref`. This is transparent and handles arbitrarily
24//!    deep chains with near-zero overhead when the stack is sufficient.
25//! 2. **[`collapse`](ArcCoyoneda::collapse) (manual).** Call `collapse()` periodically
26//!    to flatten accumulated layers. Requires `F: Functor`.
27//! 3. **[`CoyonedaExplicit`](crate::types::CoyonedaExplicit) with `.boxed()`.** An
28//!    alternative that accumulates maps without adding recursion depth.
29//!
30//! ## HKT limitations
31//!
32//! `ArcCoyonedaBrand` does **not** implement [`Functor`](crate::classes::Functor).
33//! The HKT trait signatures lack `Send + Sync` bounds on their closure parameters,
34//! so there is no way to guarantee that closures passed to `map` are safe to store
35//! inside an `Arc`-wrapped layer. Use [`RcCoyonedaBrand`](crate::brands::RcCoyonedaBrand)
36//! when HKT polymorphism is needed, or work with `ArcCoyoneda` directly through its
37//! inherent methods.
38//!
39//! ### Examples
40//!
41//! ```
42//! use fp_library::{
43//! 	brands::*,
44//! 	types::*,
45//! };
46//!
47//! let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).map(|x| x * 2);
48//!
49//! // Send + Sync: can cross thread boundaries.
50//! let coyo2 = coyo.clone();
51//! let handle = std::thread::spawn(move || coyo2.lower_ref());
52//! assert_eq!(handle.join().unwrap(), vec![4, 6, 8]);
53//! assert_eq!(coyo.lower_ref(), vec![4, 6, 8]);
54//! ```
55
56#[fp_macros::document_module]
57mod inner {
58	use {
59		crate::{
60			brands::ArcCoyonedaBrand,
61			classes::{
62				Lift,
63				NaturalTransformation,
64				*,
65			},
66			impl_kind,
67			kinds::*,
68		},
69		fp_macros::*,
70		std::sync::Arc,
71	};
72
73	// -- Inner trait (borrow-based lowering for Arc, requires Send + Sync) --
74
75	/// Trait for lowering an `ArcCoyoneda` value back to its underlying functor
76	/// via a shared reference. Requires `Send + Sync` for thread safety.
77	#[document_type_parameters(
78		"The lifetime of the values.",
79		"The brand of the underlying type constructor.",
80		"The output type of the accumulated mapping function."
81	)]
82	#[document_parameters("The trait object reference.")]
83	trait ArcCoyonedaLowerRef<'a, F, A: 'a>: Send + Sync + 'a
84	where
85		F: Kind_cdc7cd43dac7585f + 'a, {
86		/// Lower to the concrete functor by applying accumulated functions via `F::map`.
87		#[document_signature]
88		///
89		#[document_returns("The underlying functor value with accumulated functions applied.")]
90		#[document_examples]
91		///
92		/// ```
93		/// use fp_library::{
94		/// 	brands::*,
95		/// 	types::*,
96		/// };
97		///
98		/// let coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(42));
99		/// assert_eq!(coyo.lower_ref(), Some(42));
100		/// ```
101		fn lower_ref(&self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
102		where
103			F: Functor;
104	}
105
106	// -- Base layer --
107
108	/// Base layer created by [`ArcCoyoneda::lift`]. Wraps `F A` with no mapping.
109	/// Clones the underlying value on each call to `lower_ref`.
110	struct ArcCoyonedaBase<'a, F, A: 'a>
111	where
112		F: Kind_cdc7cd43dac7585f<Of<'a, A>: Send + Sync> + 'a, {
113		fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
114	}
115
116	#[document_type_parameters(
117		"The lifetime of the values.",
118		"The brand of the underlying type constructor.",
119		"The type of the value inside the functor."
120	)]
121	#[document_parameters("The base layer instance.")]
122	impl<'a, F, A: 'a> ArcCoyonedaLowerRef<'a, F, A> for ArcCoyonedaBase<'a, F, A>
123	where
124		F: Kind_cdc7cd43dac7585f + 'a,
125		Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone + Send + Sync,
126	{
127		/// Returns the wrapped value by cloning.
128		#[document_signature]
129		///
130		#[document_returns("A clone of the underlying functor value.")]
131		#[document_examples]
132		///
133		/// ```
134		/// use fp_library::{
135		/// 	brands::*,
136		/// 	types::*,
137		/// };
138		///
139		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
140		/// assert_eq!(coyo.lower_ref(), vec![1, 2, 3]);
141		/// ```
142		fn lower_ref(&self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
143		where
144			F: Functor, {
145			self.fa.clone()
146		}
147	}
148
149	// -- Map layer --
150
151	/// Map layer created by [`ArcCoyoneda::map`]. Stores the inner value (Arc-wrapped)
152	/// and an Arc-wrapped function to apply at lower time.
153	struct ArcCoyonedaMapLayer<'a, F, B: 'a, A: 'a>
154	where
155		F: Kind_cdc7cd43dac7585f + 'a, {
156		inner: Arc<dyn ArcCoyonedaLowerRef<'a, F, B> + 'a>,
157		func: Arc<dyn Fn(B) -> A + Send + Sync + 'a>,
158	}
159
160	// Send + Sync auto-derived: both fields are `Arc<dyn ... + Send + Sync>`.
161	// `F` only appears inside erased trait object bounds, not as concrete field
162	// data, so the compiler does not need `F::Of` to be `Send`/`Sync`.
163	// Compile-time assertions at the bottom of this module guard against regressions.
164
165	#[document_type_parameters(
166		"The lifetime of the values.",
167		"The brand of the underlying type constructor.",
168		"The input type of this layer's mapping function.",
169		"The output type of this layer's mapping function."
170	)]
171	#[document_parameters("The map layer instance.")]
172	impl<'a, F, B: 'a, A: 'a> ArcCoyonedaLowerRef<'a, F, A> for ArcCoyonedaMapLayer<'a, F, B, A>
173	where
174		F: Kind_cdc7cd43dac7585f + 'a,
175	{
176		/// Lowers the inner value, then applies this layer's function via `F::map`.
177		#[document_signature]
178		///
179		#[document_returns("The underlying functor value with this layer's function applied.")]
180		#[document_examples]
181		///
182		/// ```
183		/// use fp_library::{
184		/// 	brands::*,
185		/// 	types::*,
186		/// };
187		///
188		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
189		/// assert_eq!(coyo.lower_ref(), vec![2, 3, 4]);
190		/// ```
191		fn lower_ref(&self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
192		where
193			F: Functor, {
194			#[cfg(feature = "stacker")]
195			{
196				stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
197					let lowered = self.inner.lower_ref();
198					let func = self.func.clone();
199					F::map(move |b| (*func)(b), lowered)
200				})
201			}
202			#[cfg(not(feature = "stacker"))]
203			{
204				let lowered = self.inner.lower_ref();
205				let func = self.func.clone();
206				F::map(move |b| (*func)(b), lowered)
207			}
208		}
209	}
210
211	// -- New layer: wraps F<B> and an Arc-wrapped function (single allocation) --
212
213	/// New layer created by [`ArcCoyoneda::new`]. Stores the functor value and an
214	/// Arc-wrapped function, implementing `ArcCoyonedaLowerRef` directly in a single
215	/// Arc allocation.
216	struct ArcCoyonedaNewLayer<'a, F, B: 'a, A: 'a>
217	where
218		F: Kind_cdc7cd43dac7585f<Of<'a, B>: Send + Sync> + 'a, {
219		fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
220		func: Arc<dyn Fn(B) -> A + Send + Sync + 'a>,
221	}
222
223	#[document_type_parameters(
224		"The lifetime of the values.",
225		"The brand of the underlying type constructor.",
226		"The input type of the stored function.",
227		"The output type of the stored function."
228	)]
229	#[document_parameters("The new layer instance.")]
230	impl<'a, F, B: 'a, A: 'a> ArcCoyonedaLowerRef<'a, F, A> for ArcCoyonedaNewLayer<'a, F, B, A>
231	where
232		F: Kind_cdc7cd43dac7585f + 'a,
233		Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>): Clone + Send + Sync,
234	{
235		/// Applies the stored function to the stored functor value via `F::map`.
236		#[document_signature]
237		///
238		#[document_returns("The underlying functor value with the stored function applied.")]
239		#[document_examples]
240		///
241		/// ```
242		/// use fp_library::{
243		/// 	brands::*,
244		/// 	types::*,
245		/// };
246		///
247		/// let coyo = ArcCoyoneda::<VecBrand, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
248		/// assert_eq!(coyo.lower_ref(), vec![2, 4, 6]);
249		/// ```
250		fn lower_ref(&self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
251		where
252			F: Functor, {
253			let func = self.func.clone();
254			F::map(move |b| (*func)(b), self.fb.clone())
255		}
256	}
257
258	// -- Outer type --
259
260	/// Thread-safe reference-counted free functor with `Clone` support.
261	///
262	/// `ArcCoyoneda` wraps its inner layers in [`Arc`], making the entire structure
263	/// cheaply cloneable, `Send`, and `Sync`.
264	///
265	/// See the [module documentation](crate::types::arc_coyoneda) for trade-offs
266	/// and examples.
267	#[document_type_parameters(
268		"The lifetime of the values.",
269		"The brand of the underlying type constructor.",
270		"The current output type."
271	)]
272	pub struct ArcCoyoneda<'a, F, A: 'a>(Arc<dyn ArcCoyonedaLowerRef<'a, F, A> + 'a>)
273	where
274		F: Kind_cdc7cd43dac7585f + 'a;
275
276	#[document_type_parameters(
277		"The lifetime of the values.",
278		"The brand of the underlying type constructor.",
279		"The current output type."
280	)]
281	#[document_parameters("The `ArcCoyoneda` instance to clone.")]
282	impl<'a, F, A: 'a> Clone for ArcCoyoneda<'a, F, A>
283	where
284		F: Kind_cdc7cd43dac7585f + 'a,
285	{
286		/// Clones the `ArcCoyoneda` by bumping the atomic reference count. O(1).
287		#[document_signature]
288		///
289		#[document_returns("A new `ArcCoyoneda` sharing the same inner layers.")]
290		#[document_examples]
291		///
292		/// ```
293		/// use fp_library::{
294		/// 	brands::*,
295		/// 	types::*,
296		/// };
297		///
298		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
299		/// let coyo2 = coyo.clone();
300		/// assert_eq!(coyo.lower_ref(), coyo2.lower_ref());
301		/// ```
302		fn clone(&self) -> Self {
303			ArcCoyoneda(self.0.clone())
304		}
305	}
306
307	#[document_type_parameters(
308		"The lifetime of the values.",
309		"The brand of the underlying type constructor.",
310		"The current output type."
311	)]
312	#[document_parameters("The `ArcCoyoneda` instance.")]
313	impl<'a, F, A: 'a> ArcCoyoneda<'a, F, A>
314	where
315		F: Kind_cdc7cd43dac7585f + 'a,
316	{
317		/// Lift a value of `F A` into `ArcCoyoneda F A`.
318		///
319		/// Requires `F::Of<'a, A>: Clone + Send + Sync` because
320		/// [`lower_ref`](ArcCoyoneda::lower_ref) borrows `&self` and must produce
321		/// an owned value by cloning, and `Arc` requires thread safety.
322		#[document_signature]
323		///
324		#[document_parameters("The functor value to lift.")]
325		///
326		#[document_returns("An `ArcCoyoneda` wrapping the value.")]
327		#[document_examples]
328		///
329		/// ```
330		/// use fp_library::{
331		/// 	brands::*,
332		/// 	types::*,
333		/// };
334		///
335		/// let coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(42));
336		/// assert_eq!(coyo.lower_ref(), Some(42));
337		/// ```
338		pub fn lift(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> Self
339		where
340			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone + Send + Sync, {
341			ArcCoyoneda(Arc::new(ArcCoyonedaBase {
342				fa,
343			}))
344		}
345
346		/// Lower the `ArcCoyoneda` back to the underlying functor `F`.
347		///
348		/// Applies accumulated mapping functions via `F::map`. Requires `F: Functor`.
349		#[document_signature]
350		///
351		#[document_returns("The underlying functor value with all accumulated functions applied.")]
352		#[document_examples]
353		///
354		/// ```
355		/// use fp_library::{
356		/// 	brands::*,
357		/// 	types::*,
358		/// };
359		///
360		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
361		/// assert_eq!(coyo.lower_ref(), vec![2, 3, 4]);
362		/// ```
363		pub fn lower_ref(&self) -> Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
364		where
365			F: Functor, {
366			self.0.lower_ref()
367		}
368
369		/// Flatten accumulated map layers into a single base layer.
370		///
371		/// Resets the recursion depth by lowering and re-lifting. Useful for
372		/// preventing stack overflow in deep chains when the `stacker` feature
373		/// is not enabled.
374		///
375		/// Requires `F: Functor` (for lowering) and `F::Of<'a, A>: Clone + Send + Sync`
376		/// (for re-lifting).
377		#[document_signature]
378		///
379		#[document_returns("A new `ArcCoyoneda` with a single base layer.")]
380		#[document_examples]
381		///
382		/// ```
383		/// use fp_library::{
384		/// 	brands::*,
385		/// 	types::*,
386		/// };
387		///
388		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1).map(|x| x * 2);
389		/// let collapsed = coyo.collapse();
390		/// assert_eq!(collapsed.lower_ref(), vec![4, 6, 8]);
391		/// ```
392		pub fn collapse(&self) -> ArcCoyoneda<'a, F, A>
393		where
394			F: Functor,
395			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone + Send + Sync, {
396			ArcCoyoneda::lift(self.lower_ref())
397		}
398
399		/// Fold the `ArcCoyoneda` by lowering and delegating to `F::fold_map`.
400		///
401		/// Non-consuming alternative to the `Foldable` trait method, which takes
402		/// the value by move. This borrows `&self` via `lower_ref`.
403		#[document_signature]
404		///
405		#[document_type_parameters(
406			"The brand of the cloneable function to use.",
407			"The type of the monoid."
408		)]
409		///
410		#[document_parameters("The function to map each element to a monoid.")]
411		///
412		#[document_returns("The combined monoid value.")]
413		#[document_examples]
414		///
415		/// ```
416		/// use fp_library::{
417		/// 	brands::*,
418		/// 	types::*,
419		/// };
420		///
421		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
422		/// let result = coyo.fold_map::<RcFnBrand, _>(|x: i32| x.to_string());
423		/// assert_eq!(result, "102030");
424		/// // Can still use coyo after folding.
425		/// assert_eq!(coyo.lower_ref(), vec![10, 20, 30]);
426		/// ```
427		pub fn fold_map<FnBrand: LiftFn + 'a, M>(
428			&self,
429			func: impl Fn(A) -> M + 'a,
430		) -> M
431		where
432			F: Functor + Foldable,
433			A: Clone,
434			M: Monoid + 'a, {
435			F::fold_map::<FnBrand, A, M>(func, self.lower_ref())
436		}
437
438		/// Map a function over the `ArcCoyoneda` value.
439		///
440		/// The function must be `Send + Sync` for thread safety. It is wrapped in
441		/// [`Arc`] so it does not need to implement `Clone`.
442		#[document_signature]
443		///
444		#[document_type_parameters("The new output type after applying the function.")]
445		///
446		#[document_parameters("The function to apply.")]
447		///
448		#[document_returns(
449			"A new `ArcCoyoneda` with the function stored for deferred application."
450		)]
451		#[document_examples]
452		///
453		/// ```
454		/// use fp_library::{
455		/// 	brands::*,
456		/// 	types::*,
457		/// };
458		///
459		/// let coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x * 2).map(|x| x + 1);
460		/// assert_eq!(coyo.lower_ref(), Some(11));
461		/// ```
462		pub fn map<B: 'a>(
463			self,
464			f: impl Fn(A) -> B + Send + Sync + 'a,
465		) -> ArcCoyoneda<'a, F, B> {
466			ArcCoyoneda(Arc::new(ArcCoyonedaMapLayer {
467				inner: self.0,
468				func: Arc::new(f),
469			}))
470		}
471
472		/// Create an `ArcCoyoneda` from a function and a functor value.
473		///
474		/// This is more efficient than `lift(fb).map(f)` because it creates
475		/// a single layer instead of two.
476		#[document_signature]
477		///
478		#[document_type_parameters("The input type of the function.")]
479		///
480		#[document_parameters("The function to defer.", "The functor value.")]
481		///
482		#[document_returns("An `ArcCoyoneda` wrapping the value with the deferred function.")]
483		#[document_examples]
484		///
485		/// ```
486		/// use fp_library::{
487		/// 	brands::*,
488		/// 	types::*,
489		/// };
490		///
491		/// let coyo = ArcCoyoneda::<VecBrand, _>::new(|x: i32| x * 2, vec![1, 2, 3]);
492		/// assert_eq!(coyo.lower_ref(), vec![2, 4, 6]);
493		/// ```
494		pub fn new<B: 'a>(
495			f: impl Fn(B) -> A + Send + Sync + 'a,
496			fb: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
497		) -> Self
498		where
499			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>): Clone + Send + Sync, {
500			ArcCoyoneda(Arc::new(ArcCoyonedaNewLayer {
501				fb,
502				func: Arc::new(f),
503			}))
504		}
505
506		/// Apply a natural transformation to change the underlying functor.
507		///
508		/// Lowers to `F A` (applying all accumulated maps), transforms via the
509		/// natural transformation, then re-lifts into `ArcCoyoneda G A`.
510		///
511		/// Requires `F: Functor` for lowering.
512		#[document_signature]
513		///
514		#[document_type_parameters("The target functor brand.")]
515		///
516		#[document_parameters("The natural transformation from `F` to `G`.")]
517		///
518		#[document_returns("A new `ArcCoyoneda` over the target functor `G`.")]
519		#[document_examples]
520		///
521		/// ```
522		/// use fp_library::{
523		/// 	brands::*,
524		/// 	classes::*,
525		/// 	types::*,
526		/// };
527		///
528		/// struct VecToOption;
529		/// impl NaturalTransformation<VecBrand, OptionBrand> for VecToOption {
530		/// 	fn transform<'a, A: 'a>(
531		/// 		&self,
532		/// 		fa: Vec<A>,
533		/// 	) -> Option<A> {
534		/// 		fa.into_iter().next()
535		/// 	}
536		/// }
537		///
538		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![10, 20, 30]);
539		/// let hoisted = coyo.hoist(VecToOption);
540		/// assert_eq!(hoisted.lower_ref(), Some(10));
541		/// ```
542		pub fn hoist<G: Kind_cdc7cd43dac7585f + 'a>(
543			self,
544			nat: impl NaturalTransformation<F, G>,
545		) -> ArcCoyoneda<'a, G, A>
546		where
547			F: Functor,
548			Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone + Send + Sync, {
549			ArcCoyoneda::lift(nat.transform(self.lower_ref()))
550		}
551
552		/// Wrap a value in the `ArcCoyoneda` context by delegating to `F::pure`.
553		///
554		/// Requires `F::Of<'a, A>: Clone + Send + Sync` for the base layer.
555		#[document_signature]
556		///
557		#[document_parameters("The value to wrap.")]
558		///
559		#[document_returns("An `ArcCoyoneda` containing the pure value.")]
560		#[document_examples]
561		///
562		/// ```
563		/// use fp_library::{
564		/// 	brands::*,
565		/// 	types::*,
566		/// };
567		///
568		/// let coyo = ArcCoyoneda::<OptionBrand, i32>::pure(42);
569		/// assert_eq!(coyo.lower_ref(), Some(42));
570		/// ```
571		pub fn pure(a: A) -> Self
572		where
573			F: Pointed,
574			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone + Send + Sync, {
575			ArcCoyoneda::lift(F::pure(a))
576		}
577
578		/// Chain `ArcCoyoneda` computations by lowering, binding, and re-lifting.
579		///
580		/// This is a fusion barrier: all accumulated maps are applied before binding.
581		///
582		/// Requires `F: Functor` (for lowering) and `F: Semimonad` (for binding).
583		#[document_signature]
584		///
585		#[document_type_parameters("The output type of the bound computation.")]
586		///
587		#[document_parameters(
588			"The function to apply to the inner value, returning a new `ArcCoyoneda`."
589		)]
590		///
591		#[document_returns("A new `ArcCoyoneda` containing the bound result.")]
592		#[document_examples]
593		///
594		/// ```
595		/// use fp_library::{
596		/// 	brands::*,
597		/// 	types::*,
598		/// };
599		///
600		/// let coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(5));
601		/// let result = coyo.bind(|x| ArcCoyoneda::<OptionBrand, _>::lift(Some(x * 2)));
602		/// assert_eq!(result.lower_ref(), Some(10));
603		/// ```
604		pub fn bind<B: 'a>(
605			self,
606			func: impl Fn(A) -> ArcCoyoneda<'a, F, B> + 'a,
607		) -> ArcCoyoneda<'a, F, B>
608		where
609			F: Functor + Semimonad,
610			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>): Clone + Send + Sync, {
611			ArcCoyoneda::lift(F::bind(self.lower_ref(), move |a| func(a).lower_ref()))
612		}
613
614		/// Apply a function inside an `ArcCoyoneda` to a value inside another.
615		///
616		/// Both arguments are lowered and delegated to `F::apply`, then re-lifted.
617		/// This is a fusion barrier.
618		#[document_signature]
619		///
620		#[document_type_parameters(
621			"The brand of the cloneable function wrapper.",
622			"The type of the function input.",
623			"The type of the function output."
624		)]
625		///
626		#[document_parameters(
627			"The `ArcCoyoneda` containing the function(s).",
628			"The `ArcCoyoneda` containing the value(s)."
629		)]
630		///
631		#[document_returns("A new `ArcCoyoneda` containing the applied result(s).")]
632		#[document_examples]
633		///
634		/// ```
635		/// use fp_library::{
636		/// 	brands::*,
637		/// 	types::*,
638		/// };
639		///
640		/// // For thread-safe functors, prefer lift2 over apply.
641		/// // The apply method requires CloneFn::Of to be Send + Sync,
642		/// // which standard FnBrands do not satisfy (CloneFn::Of wraps
643		/// // dyn Fn without Send + Sync bounds). Use lift2 instead:
644		/// let a = ArcCoyoneda::<OptionBrand, _>::lift(Some(3));
645		/// let b = ArcCoyoneda::<OptionBrand, _>::lift(Some(4));
646		/// let result = a.lift2(|x, y| x + y, b);
647		/// assert_eq!(result.lower_ref(), Some(7));
648		/// ```
649		pub fn apply<FnBrand: LiftFn + 'a, B: Clone + 'a, C: 'a>(
650			ff: ArcCoyoneda<'a, F, <FnBrand as CloneFn>::Of<'a, B, C>>,
651			fa: ArcCoyoneda<'a, F, B>,
652		) -> ArcCoyoneda<'a, F, C>
653		where
654			F: Functor + Semiapplicative,
655			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone + Send + Sync, {
656			ArcCoyoneda::lift(F::apply::<FnBrand, B, C>(ff.lower_ref(), fa.lower_ref()))
657		}
658
659		/// Lift a binary function into the `ArcCoyoneda` context.
660		///
661		/// Both arguments are lowered and delegated to `F::lift2`, then re-lifted.
662		/// This is a fusion barrier.
663		#[document_signature]
664		///
665		#[document_type_parameters("The type of the second value.", "The type of the result.")]
666		///
667		#[document_parameters("The binary function to apply.", "The second `ArcCoyoneda` value.")]
668		///
669		#[document_returns("An `ArcCoyoneda` containing the result.")]
670		#[document_examples]
671		///
672		/// ```
673		/// use fp_library::{
674		/// 	brands::*,
675		/// 	types::*,
676		/// };
677		///
678		/// let a = ArcCoyoneda::<OptionBrand, _>::lift(Some(3));
679		/// let b = ArcCoyoneda::<OptionBrand, _>::lift(Some(4));
680		/// let result = a.lift2(|x, y| x + y, b);
681		/// assert_eq!(result.lower_ref(), Some(7));
682		/// ```
683		pub fn lift2<B: Clone + 'a, C: 'a>(
684			self,
685			func: impl Fn(A, B) -> C + 'a,
686			fb: ArcCoyoneda<'a, F, B>,
687		) -> ArcCoyoneda<'a, F, C>
688		where
689			F: Functor + Lift,
690			A: Clone,
691			Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>): Clone + Send + Sync, {
692			ArcCoyoneda::lift(F::lift2(func, self.lower_ref(), fb.lower_ref()))
693		}
694	}
695
696	// -- Brand --
697
698	impl_kind! {
699		impl<F: Kind_cdc7cd43dac7585f + 'static> for ArcCoyonedaBrand<F> {
700			type Of<'a, A: 'a>: 'a = ArcCoyoneda<'a, F, A>;
701		}
702	}
703
704	// -- Brand-level type class instances --
705	//
706	// ArcCoyonedaBrand implements only Foldable. It does NOT implement Functor,
707	// Pointed, Lift, Semiapplicative, or Semimonad, for two independent reasons:
708	//
709	// 1. Functor: the HKT Functor::map signature lacks Send + Sync bounds on its
710	//    closure parameter, so closures passed to map cannot be stored inside
711	//    Arc-wrapped layers. This is the same limitation as SendThunkBrand.
712	//
713	// 2. Pointed, Lift, Semiapplicative, Semimonad: even if Functor were available,
714	//    these traits require constructing an ArcCoyoneda, which needs
715	//    F::Of<'a, A>: Clone + Send + Sync. This bound cannot be expressed in the
716	//    trait method signatures (same blocker as RcCoyonedaBrand; see rc_coyoneda.rs).
717	//
718	// Use RcCoyonedaBrand when HKT polymorphism is needed, or work with ArcCoyoneda
719	// directly via its inherent methods (pure, apply, bind, lift2).
720
721	// -- Foldable implementation --
722
723	#[document_type_parameters("The brand of the underlying foldable functor.")]
724	impl<F: Functor + Foldable + 'static> Foldable for ArcCoyonedaBrand<F> {
725		/// Folds the `ArcCoyoneda` by lowering to the underlying functor and delegating.
726		///
727		/// Requires `F: Functor` (for lowering) and `F: Foldable`.
728		#[document_signature]
729		///
730		#[document_type_parameters(
731			"The lifetime of the elements.",
732			"The brand of the cloneable function to use.",
733			"The type of the elements in the structure.",
734			"The type of the monoid."
735		)]
736		///
737		#[document_parameters(
738			"The function to map each element to a monoid.",
739			"The `ArcCoyoneda` structure to fold."
740		)]
741		///
742		#[document_returns("The combined monoid value.")]
743		#[document_examples]
744		///
745		/// ```
746		/// use fp_library::{
747		/// 	brands::*,
748		/// 	functions::*,
749		/// 	types::*,
750		/// };
751		///
752		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
753		/// let result = explicit::fold_map::<RcFnBrand, ArcCoyonedaBrand<VecBrand>, _, _, _, _>(
754		/// 	|x: i32| x.to_string(),
755		/// 	coyo,
756		/// );
757		/// assert_eq!(result, "102030".to_string());
758		/// ```
759		fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
760			func: impl Fn(A) -> M + 'a,
761			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
762		) -> M
763		where
764			M: Monoid + 'a,
765			FnBrand: LiftFn + 'a, {
766			F::fold_map::<FnBrand, A, M>(func, fa.lower_ref())
767		}
768	}
769
770	// -- Debug --
771
772	#[document_type_parameters(
773		"The lifetime of the values.",
774		"The brand of the underlying type constructor.",
775		"The current output type."
776	)]
777	#[document_parameters("The `ArcCoyoneda` instance.")]
778	impl<'a, F, A: 'a> core::fmt::Debug for ArcCoyoneda<'a, F, A>
779	where
780		F: Kind_cdc7cd43dac7585f + 'a,
781	{
782		/// Formats the `ArcCoyoneda` as an opaque value.
783		///
784		/// The inner layers and functions cannot be inspected, so the output
785		/// is always `ArcCoyoneda(<opaque>)`.
786		#[document_signature]
787		///
788		#[document_parameters("The formatter.")]
789		///
790		#[document_returns("The formatting result.")]
791		#[document_examples]
792		///
793		/// ```
794		/// use fp_library::{
795		/// 	brands::*,
796		/// 	types::*,
797		/// };
798		///
799		/// let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]);
800		/// assert_eq!(format!("{:?}", coyo), "ArcCoyoneda(<opaque>)");
801		/// ```
802		fn fmt(
803			&self,
804			f: &mut core::fmt::Formatter<'_>,
805		) -> core::fmt::Result {
806			f.write_str("ArcCoyoneda(<opaque>)")
807		}
808	}
809
810	// -- From<ArcCoyoneda> for Coyoneda --
811
812	#[document_type_parameters(
813		"The lifetime of the values.",
814		"The brand of the underlying functor.",
815		"The type of the values."
816	)]
817	impl<'a, F, A: 'a> From<ArcCoyoneda<'a, F, A>> for crate::types::Coyoneda<'a, F, A>
818	where
819		F: Kind_cdc7cd43dac7585f + Functor + 'a,
820	{
821		/// Convert an [`ArcCoyoneda`] into a [`Coyoneda`](crate::types::Coyoneda)
822		/// by lowering to the underlying functor and re-lifting.
823		///
824		/// This applies all accumulated maps via `F::map` and clones the base value.
825		#[document_signature]
826		///
827		#[document_parameters("The `ArcCoyoneda` to convert.")]
828		///
829		#[document_returns("A `Coyoneda` containing the lowered value.")]
830		#[document_examples]
831		///
832		/// ```
833		/// use fp_library::{
834		/// 	brands::*,
835		/// 	types::*,
836		/// };
837		///
838		/// let arc_coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(5)).map(|x| x + 1);
839		/// let coyo: Coyoneda<OptionBrand, i32> = arc_coyo.into();
840		/// assert_eq!(coyo.lower(), Some(6));
841		/// ```
842		fn from(arc: ArcCoyoneda<'a, F, A>) -> Self {
843			crate::types::Coyoneda::lift(arc.lower_ref())
844		}
845	}
846
847	// -- Compile-time assertions for Send/Sync --
848
849	// These assertions verify that the compiler auto-derives Send/Sync
850	// correctly for each layer type. If any field type changes in a way
851	// that breaks Send/Sync, these assertions will fail at compile time.
852	const _: () = {
853		fn _assert_send<T: Send>() {}
854		fn _assert_sync<T: Sync>() {}
855
856		// ArcCoyonedaBase: Send/Sync via associated type bounds on Kind
857		fn _check_base<'a, F: Kind_cdc7cd43dac7585f<Of<'a, A>: Send + Sync> + 'a, A: 'a>() {
858			_assert_send::<ArcCoyonedaBase<'a, F, A>>();
859			_assert_sync::<ArcCoyonedaBase<'a, F, A>>();
860		}
861
862		// ArcCoyonedaMapLayer: unconditionally Send + Sync
863		// (both fields are Arc<dyn ... + Send + Sync>)
864		fn _check_map_layer<'a, F: Kind_cdc7cd43dac7585f + 'a, B: 'a, A: 'a>() {
865			_assert_send::<ArcCoyonedaMapLayer<'a, F, B, A>>();
866			_assert_sync::<ArcCoyonedaMapLayer<'a, F, B, A>>();
867		}
868
869		// ArcCoyonedaNewLayer: Send/Sync via associated type bounds on Kind
870		fn _check_new_layer<
871			'a,
872			F: Kind_cdc7cd43dac7585f<Of<'a, B>: Send + Sync> + 'a,
873			B: 'a,
874			A: 'a,
875		>() {
876			_assert_send::<ArcCoyonedaNewLayer<'a, F, B, A>>();
877			_assert_sync::<ArcCoyonedaNewLayer<'a, F, B, A>>();
878		}
879	};
880}
881
882pub use inner::*;
883
884#[cfg(test)]
885#[expect(clippy::unwrap_used, reason = "Tests use panicking operations for brevity and clarity")]
886mod tests {
887	use crate::{
888		brands::*,
889		functions::*,
890		types::*,
891	};
892
893	#[test]
894	fn lift_lower_ref_identity() {
895		let coyo = ArcCoyoneda::<OptionBrand, _>::lift(Some(42));
896		assert_eq!(coyo.lower_ref(), Some(42));
897	}
898
899	#[test]
900	fn chained_maps() {
901		let result = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3])
902			.map(|x| x + 1)
903			.map(|x| x * 2)
904			.lower_ref();
905		assert_eq!(result, vec![4, 6, 8]);
906	}
907
908	#[test]
909	fn clone_and_lower() {
910		let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x + 1);
911		let coyo2 = coyo.clone();
912		assert_eq!(coyo.lower_ref(), vec![2, 3, 4]);
913		assert_eq!(coyo2.lower_ref(), vec![2, 3, 4]);
914	}
915
916	#[test]
917	fn send_across_thread() {
918		let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
919		let handle = std::thread::spawn(move || coyo.lower_ref());
920		assert_eq!(handle.join().unwrap(), vec![10, 20, 30]);
921	}
922
923	#[test]
924	fn fold_map_on_mapped() {
925		let coyo = ArcCoyoneda::<VecBrand, _>::lift(vec![1, 2, 3]).map(|x| x * 10);
926		let result = explicit::fold_map::<RcFnBrand, ArcCoyonedaBrand<VecBrand>, _, _, _, _>(
927			|x: i32| x.to_string(),
928			coyo,
929		);
930		assert_eq!(result, "102030".to_string());
931	}
932
933	#[test]
934	fn map_on_none_stays_none() {
935		let result = ArcCoyoneda::<OptionBrand, _>::lift(None::<i32>).map(|x| x + 1).lower_ref();
936		assert_eq!(result, None);
937	}
938
939	// -- Property-based tests --
940
941	mod property {
942		use {
943			crate::{
944				brands::*,
945				functions::*,
946				types::*,
947			},
948			quickcheck_macros::quickcheck,
949		};
950
951		#[quickcheck]
952		fn functor_identity_vec(v: Vec<i32>) -> bool {
953			let coyo = ArcCoyoneda::<VecBrand, _>::lift(v.clone());
954			coyo.map(identity).lower_ref() == v
955		}
956
957		#[quickcheck]
958		fn functor_identity_option(x: Option<i32>) -> bool {
959			let coyo = ArcCoyoneda::<OptionBrand, _>::lift(x);
960			coyo.map(identity).lower_ref() == x
961		}
962
963		#[quickcheck]
964		fn functor_composition_vec(v: Vec<i32>) -> bool {
965			let f = |x: i32| x.wrapping_add(1);
966			let g = |x: i32| x.wrapping_mul(2);
967
968			let left = ArcCoyoneda::<VecBrand, _>::lift(v.clone()).map(compose(f, g)).lower_ref();
969			let right = ArcCoyoneda::<VecBrand, _>::lift(v).map(g).map(f).lower_ref();
970			left == right
971		}
972
973		#[quickcheck]
974		fn functor_composition_option(x: Option<i32>) -> bool {
975			let f = |x: i32| x.wrapping_add(1);
976			let g = |x: i32| x.wrapping_mul(2);
977
978			let left = ArcCoyoneda::<OptionBrand, _>::lift(x).map(compose(f, g)).lower_ref();
979			let right = ArcCoyoneda::<OptionBrand, _>::lift(x).map(g).map(f).lower_ref();
980			left == right
981		}
982
983		#[quickcheck]
984		fn collapse_preserves_value(v: Vec<i32>) -> bool {
985			let coyo = ArcCoyoneda::<VecBrand, _>::lift(v)
986				.map(|x: i32| x.wrapping_add(1))
987				.map(|x: i32| x.wrapping_mul(2));
988			let before = coyo.lower_ref();
989			let after = coyo.collapse().lower_ref();
990			before == after
991		}
992
993		#[quickcheck]
994		fn foldable_consistency_vec(v: Vec<i32>) -> bool {
995			let coyo = ArcCoyoneda::<VecBrand, _>::lift(v.clone()).map(|x: i32| x.wrapping_add(1));
996			let via_coyoneda: String =
997				explicit::fold_map::<RcFnBrand, ArcCoyonedaBrand<VecBrand>, _, _, _, _>(
998					|x: i32| x.to_string(),
999					coyo,
1000				);
1001			let direct: String = explicit::fold_map::<RcFnBrand, VecBrand, _, _, _, _>(
1002				|x: i32| x.to_string(),
1003				v.iter().map(|x| x.wrapping_add(1)).collect::<Vec<_>>(),
1004			);
1005			via_coyoneda == direct
1006		}
1007	}
1008}