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, _>(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
58//! let x = pure::<OptionBrand, _>(5);
59//! let y = apply(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, _>(lift_fn_new::<RcFnBrand, _, _>(identity::<i32>));
100	/// assert_eq!(apply(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(pure::<OptionBrand, _>(lift_fn_new::<RcFnBrand, _, _>(f)), pure::<OptionBrand, _>(5),),
106	/// 	pure::<OptionBrand, _>(f(5)),
107	/// );
108	///
109	/// // Interchange: apply(u, pure(y)) = apply(pure(|f| f(y)), u)
110	/// let u = Some(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1));
111	/// let y = 5i32;
112	/// let left = apply(u.clone(), pure::<OptionBrand, _>(y));
113	/// let apply_y = pure::<OptionBrand, _>(lift_fn_new::<RcFnBrand, _, _>(
114	/// 	move |f: std::rc::Rc<dyn Fn(i32) -> i32>| f(y),
115	/// ));
116	/// let right = apply(apply_y, u);
117	/// assert_eq!(left, right);
118	/// ```
119	pub trait Applicative: Pointed + Semiapplicative + ApplyFirst + ApplySecond {}
120
121	/// Blanket implementation of [`Applicative`].
122	#[document_type_parameters("The brand type.")]
123	impl<Brand> Applicative for Brand where Brand: Pointed + Semiapplicative + ApplyFirst + ApplySecond {}
124
125	/// Performs an applicative action when a condition is true.
126	///
127	/// Returns the given action if `condition` is `true`, otherwise returns `pure(())`.
128	#[document_signature]
129	///
130	#[document_type_parameters("The lifetime of the computation.", "The brand of the applicative.")]
131	///
132	#[document_parameters(
133		"The condition to check.",
134		"The action to perform if the condition is true."
135	)]
136	///
137	#[document_returns("The action if the condition is true, otherwise `pure(())`.")]
138	#[document_examples]
139	///
140	/// ```
141	/// use fp_library::{
142	/// 	brands::*,
143	/// 	functions::*,
144	/// };
145	///
146	/// assert_eq!(when::<OptionBrand>(true, Some(())), Some(()));
147	/// assert_eq!(when::<OptionBrand>(false, Some(())), Some(()));
148	/// assert_eq!(when::<VecBrand>(true, vec![(), ()]), vec![(), ()]);
149	/// assert_eq!(when::<VecBrand>(false, vec![(), ()]), vec![()]);
150	/// ```
151	pub fn when<'a, Brand: Applicative>(
152		condition: bool,
153		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
154	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
155		if condition { action } else { Brand::pure(()) }
156	}
157
158	/// Performs an applicative action unless a condition is true.
159	///
160	/// Returns the given action if `condition` is `false`, otherwise returns `pure(())`.
161	#[document_signature]
162	///
163	#[document_type_parameters("The lifetime of the computation.", "The brand of the applicative.")]
164	///
165	#[document_parameters(
166		"The condition to check.",
167		"The action to perform if the condition is false."
168	)]
169	///
170	#[document_returns("The action if the condition is false, otherwise `pure(())`.")]
171	#[document_examples]
172	///
173	/// ```
174	/// use fp_library::{
175	/// 	brands::*,
176	/// 	functions::*,
177	/// };
178	///
179	/// assert_eq!(unless::<OptionBrand>(false, Some(())), Some(()));
180	/// assert_eq!(unless::<OptionBrand>(true, Some(())), Some(()));
181	/// assert_eq!(unless::<VecBrand>(false, vec![(), ()]), vec![(), ()]);
182	/// assert_eq!(unless::<VecBrand>(true, vec![(), ()]), vec![()]);
183	/// ```
184	pub fn unless<'a, Brand: Applicative>(
185		condition: bool,
186		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
187	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
188		if !condition { action } else { Brand::pure(()) }
189	}
190}
191
192pub use inner::*;