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::*;