Skip to main content

fp_library/classes/
applicative.rs

1//! Applicative functors, allowing for independent computations to be combined within a context.
2//!
3//! An applicative functor combines [`Pointed`][crate::classes::Pointed] (for lifting values
4//! with [`pure`][crate::functions::pure]) and [`Semiapplicative`][crate::classes::Semiapplicative]
5//! (for applying wrapped functions with [`apply`][crate::functions::apply]).
6//! The [`a_do!`][fp_macros::a_do] macro provides applicative do-notation for combining
7//! independent computations in a flat, readable style.
8//!
9//! ### Examples
10//!
11//! Combining independent computations with [`a_do!`][fp_macros::a_do]:
12//!
13//! ```
14//! use fp_library::{brands::*, functions::*};
15//! use fp_macros::a_do;
16//!
17//! // Bindings are independent — each is a separate computation
18//! let result = a_do!(OptionBrand {
19//! 	x <- Some(3);
20//! 	y <- Some(4);
21//! 	x + y
22//! });
23//! assert_eq!(result, Some(7));
24//!
25//! // None in any position short-circuits the whole expression
26//! let result = a_do!(OptionBrand {
27//! 	x <- Some(3);
28//! 	y: i32 <- None;
29//! 	x + y
30//! });
31//! assert_eq!(result, None);
32//! ```
33//!
34//! Combining lists (all combinations):
35//!
36//! ```
37//! use fp_library::{brands::*, functions::*};
38//! use fp_macros::a_do;
39//!
40//! let result = a_do!(VecBrand {
41//! 	x <- vec![1, 2];
42//! 	y <- vec![10, 20];
43//! 	x + y
44//! });
45//! assert_eq!(result, vec![11, 21, 12, 22]);
46//! ```
47//!
48//! Using [`apply`][crate::functions::apply] directly:
49//!
50//! ```
51//! use fp_library::{
52//! 	brands::*,
53//! 	classes::*,
54//! 	functions::*,
55//! };
56//!
57//! let f = pure::<OptionBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
58//! let x = pure::<OptionBrand, _>(5);
59//! let y = apply::<RcFnBrand, OptionBrand, _, _>(f, x);
60//! assert_eq!(y, Some(10));
61//! ```
62
63#[fp_macros::document_module]
64mod inner {
65	use {
66		crate::{
67			classes::*,
68			kinds::*,
69		},
70		fp_macros::*,
71	};
72
73	/// A type class for applicative functors, allowing for values to be wrapped
74	/// in a context and for functions within a context to be applied to values
75	/// within a context.
76	///
77	/// `class (Pointed f, Semiapplicative f) => Applicative f`
78	///
79	/// ### Laws
80	///
81	/// `Applicative` instances must satisfy the following laws:
82	/// * Identity: `apply(pure(identity), v) = v`.
83	/// * Composition: `apply(apply(map(|f| |g| compose(f, g), u), v), w) = apply(u, apply(v, w))`.
84	/// * Homomorphism: `apply(pure(f), pure(x)) = pure(f(x))`.
85	/// * Interchange: `apply(u, pure(y)) = apply(pure(|f| f(y)), u)`.
86	#[document_examples]
87	///
88	/// Applicative laws for [`Option`]:
89	///
90	/// ```
91	/// use fp_library::{
92	/// 	brands::*,
93	/// 	classes::*,
94	/// 	functions::*,
95	/// };
96	///
97	/// // Identity: apply(pure(identity), v) = v
98	/// let v = Some(5);
99	/// let id_fn = pure::<OptionBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(identity::<i32>));
100	/// assert_eq!(apply::<RcFnBrand, OptionBrand, _, _>(id_fn, v), v);
101	///
102	/// // Homomorphism: apply(pure(f), pure(x)) = pure(f(x))
103	/// let f = |x: i32| x * 2;
104	/// assert_eq!(
105	/// 	apply::<RcFnBrand, OptionBrand, _, _>(
106	/// 		pure::<OptionBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(f)),
107	/// 		pure::<OptionBrand, _>(5),
108	/// 	),
109	/// 	pure::<OptionBrand, _>(f(5)),
110	/// );
111	///
112	/// // Interchange: apply(u, pure(y)) = apply(pure(|f| f(y)), u)
113	/// let u = Some(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1));
114	/// let y = 5i32;
115	/// let left = apply::<RcFnBrand, OptionBrand, _, _>(u.clone(), pure::<OptionBrand, _>(y));
116	/// let apply_y = pure::<OptionBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(
117	/// 	move |f: std::rc::Rc<dyn Fn(i32) -> i32>| f(y),
118	/// ));
119	/// let right = apply::<RcFnBrand, OptionBrand, _, _>(apply_y, u);
120	/// assert_eq!(left, right);
121	/// ```
122	pub trait Applicative: Pointed + Semiapplicative + ApplyFirst + ApplySecond {}
123
124	/// Blanket implementation of [`Applicative`].
125	#[document_type_parameters("The brand type.")]
126	impl<Brand> Applicative for Brand where Brand: Pointed + Semiapplicative + ApplyFirst + ApplySecond {}
127
128	/// Performs an applicative action when a condition is true.
129	///
130	/// Returns the given action if `condition` is `true`, otherwise returns `pure(())`.
131	#[document_signature]
132	///
133	#[document_type_parameters("The lifetime of the computation.", "The brand of the applicative.")]
134	///
135	#[document_parameters(
136		"The condition to check.",
137		"The action to perform if the condition is true."
138	)]
139	///
140	#[document_returns("The action if the condition is true, otherwise `pure(())`.")]
141	#[document_examples]
142	///
143	/// ```
144	/// use fp_library::{
145	/// 	brands::*,
146	/// 	functions::*,
147	/// };
148	///
149	/// assert_eq!(when::<OptionBrand>(true, Some(())), Some(()));
150	/// assert_eq!(when::<OptionBrand>(false, Some(())), Some(()));
151	/// assert_eq!(when::<VecBrand>(true, vec![(), ()]), vec![(), ()]);
152	/// assert_eq!(when::<VecBrand>(false, vec![(), ()]), vec![()]);
153	/// ```
154	pub fn when<'a, Brand: Applicative>(
155		condition: bool,
156		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
157	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
158		if condition { action } else { Brand::pure(()) }
159	}
160
161	/// Performs an applicative action unless a condition is true.
162	///
163	/// Returns the given action if `condition` is `false`, otherwise returns `pure(())`.
164	#[document_signature]
165	///
166	#[document_type_parameters("The lifetime of the computation.", "The brand of the applicative.")]
167	///
168	#[document_parameters(
169		"The condition to check.",
170		"The action to perform if the condition is false."
171	)]
172	///
173	#[document_returns("The action if the condition is false, otherwise `pure(())`.")]
174	#[document_examples]
175	///
176	/// ```
177	/// use fp_library::{
178	/// 	brands::*,
179	/// 	functions::*,
180	/// };
181	///
182	/// assert_eq!(unless::<OptionBrand>(false, Some(())), Some(()));
183	/// assert_eq!(unless::<OptionBrand>(true, Some(())), Some(()));
184	/// assert_eq!(unless::<VecBrand>(false, vec![(), ()]), vec![(), ()]);
185	/// assert_eq!(unless::<VecBrand>(true, vec![(), ()]), vec![()]);
186	/// ```
187	pub fn unless<'a, Brand: Applicative>(
188		condition: bool,
189		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
190	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
191		if !condition { action } else { Brand::pure(()) }
192	}
193}
194
195pub use inner::*;