//! Applying functions within a context to values within a context, without an identity element.
//!
//! ### Examples
//!
//! ```
//! use fp_library::{
//! 	brands::*,
//! 	classes::*,
//! 	functions::*,
//! };
//!
//! let f = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
//! let x = Some(5);
//! let y = apply::<RcFnBrand, OptionBrand, _, _>(f, x);
//! assert_eq!(y, Some(10));
//! ```

#[fp_macros::document_module]
mod inner {
	use {
		crate::{
			classes::*,
			kinds::*,
		},
		fp_macros::*,
	};

	/// A type class for applying functions within a context to values within a context.
	///
	/// `Semiapplicative` provides the ability to apply functions that are themselves
	/// wrapped in a context to values that are also wrapped in a context.
	///
	/// ### Laws
	///
	/// `Semiapplicative` instances must satisfy the following law:
	/// * Associative composition: `apply(apply(map(|f| |g| compose(f, g), u), v), w) = apply(u, apply(v, w))`.
	#[document_examples]
	///
	/// Associative composition for [`Option`]:
	///
	/// ```
	/// use fp_library::{
	/// 	brands::*,
	/// 	classes::*,
	/// 	functions::{
	/// 		explicit::map,
	/// 		*,
	/// 	},
	/// };
	///
	/// let u = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1));
	/// let v = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
	/// let w = Some(5i32);
	///
	/// // Right side: apply(u, apply(v, w))
	/// let right = apply::<RcFnBrand, OptionBrand, _, _>(
	/// 	u.clone(),
	/// 	apply::<RcFnBrand, OptionBrand, _, _>(v.clone(), w),
	/// );
	///
	/// // Left side: apply(apply(map(|f| |g| compose(f, g), u), v), w)
	/// // Step 1: map the composition combinator over u
	/// let compose_u = map::<OptionBrand, _, _, _, _>(
	/// 	|u_fn: std::rc::Rc<dyn Fn(i32) -> i32>| {
	/// 		lift_fn_new::<RcFnBrand, _, _>(move |v_fn: std::rc::Rc<dyn Fn(i32) -> i32>| {
	/// 			let u_fn = u_fn.clone();
	/// 			lift_fn_new::<RcFnBrand, _, _>(move |x: i32| u_fn(v_fn(x)))
	/// 		})
	/// 	},
	/// 	u,
	/// );
	/// // Step 2: apply to v, then to w
	/// let composed = apply::<RcFnBrand, OptionBrand, _, _>(compose_u, v);
	/// let left = apply::<RcFnBrand, OptionBrand, _, _>(composed, w);
	///
	/// assert_eq!(left, right);
	/// assert_eq!(left, Some(11)); // (5 * 2) + 1
	/// ```
	pub trait Semiapplicative: Lift + Functor {
		/// Applies a function within a context to a value within a context.
		///
		/// This method applies a function wrapped in a context to a value wrapped in a context.
		///
		/// **Important**: This operation requires type erasure for heterogeneous functions.
		/// When a container (like `Vec`) holds multiple different closures, they must be
		/// type-erased via `Rc<dyn Fn>` or `Arc<dyn Fn>` because each Rust closure is a
		/// distinct anonymous type.
		#[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 context containing the function(s).",
			"The context containing the value(s)."
		)]
		///
		#[document_returns(
			"A new context containing the result(s) of applying the function(s) to the value(s)."
		)]
		#[document_examples]
		///
		/// ```
		/// use fp_library::{
		/// 	brands::*,
		/// 	classes::*,
		/// 	functions::*,
		/// };
		///
		/// let f = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
		/// let x = Some(5);
		/// let y = apply::<RcFnBrand, OptionBrand, _, _>(f, x);
		/// assert_eq!(y, Some(10));
		/// ```
		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>);
	}

	/// Applies a function within a context to a value within a context.
	///
	/// Free function version that dispatches to [the type class' associated function][`Semiapplicative::apply`].
	#[document_signature]
	///
	#[document_type_parameters(
		"The lifetime of the values.",
		"The brand of the cloneable function wrapper.",
		"The brand of the context.",
		"The type of the input value.",
		"The type of the output value."
	)]
	///
	#[document_parameters(
		"The context containing the function(s).",
		"The context containing the value(s)."
	)]
	///
	#[document_returns(
		"A new context containing the result(s) of applying the function(s) to the value(s)."
	)]
	#[document_examples]
	///
	/// ```
	/// use fp_library::{
	/// 	brands::*,
	/// 	classes::*,
	/// 	functions::*,
	/// };
	///
	/// let f = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
	/// let x = Some(5);
	/// let y = apply::<RcFnBrand, OptionBrand, _, _>(f, x);
	/// assert_eq!(y, Some(10));
	/// ```
	pub fn apply<'a, FnBrand: 'a + CloneFn, Brand: Semiapplicative, A: 'a + Clone, B: 'a>(
		ff: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneFn>::Of<'a, A, B>>),
		fa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
		Brand::apply::<FnBrand, A, B>(ff, fa)
	}
}

pub use inner::*;
