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