Skip to main content

fp_library/classes/
monad.rs

1//! Monads, allowing for sequencing computations where the structure depends on previous results.
2//!
3//! A monad combines [`Pointed`][crate::classes::Pointed] (for lifting values with
4//! [`pure`][crate::functions::pure]) and [`Semimonad`][crate::classes::Semimonad]
5//! (for chaining computations with [`bind`][crate::functions::bind]).
6//! `Monad` is the dual of [`Comonad`](crate::classes::Comonad): where `Comonad` composes
7//! `Extract` + `Extend`, `Monad` composes `Pointed` + `Semimonad`.
8//! The [`m_do!`][fp_macros::m_do] macro provides do-notation for writing monadic code
9//! in a flat, readable style.
10//!
11//! ### Examples
12//!
13//! Chaining fallible computations with [`Option`]:
14//!
15//! ```
16//! use fp_library::{brands::*, functions::*};
17//! use fp_macros::m_do;
18//!
19//! fn safe_div(a: i32, b: i32) -> Option<i32> {
20//! 	if b == 0 { None } else { Some(a / b) }
21//! }
22//!
23//! // Each `<-` extracts the value; None short-circuits the whole block
24//! let result = m_do!(OptionBrand {
25//! 	x <- safe_div(100, 2);
26//! 	y <- safe_div(x, 5);
27//! 	pure(y + 1)
28//! });
29//! assert_eq!(result, Some(11));
30//!
31//! // Short-circuits on failure
32//! let result = m_do!(OptionBrand {
33//! 	x <- safe_div(100, 0);
34//! 	y <- safe_div(x, 5);
35//! 	pure(y + 1)
36//! });
37//! assert_eq!(result, None);
38//! ```
39//!
40//! List comprehensions with [`Vec`]:
41//!
42//! ```
43//! use fp_library::{brands::*, functions::*};
44//! use fp_macros::m_do;
45//!
46//! // Generate Pythagorean triples up to 10
47//! let triples = m_do!(VecBrand {
48//! 	x <- (1..=10i32).collect::<Vec<_>>();
49//! 	y <- (x..=10).collect::<Vec<_>>();
50//! 	z <- (y..=10).collect::<Vec<_>>();
51//! 	_ <- if x * x + y * y == z * z { vec![()] } else { vec![] };
52//! 	pure((x, y, z))
53//! });
54//! assert_eq!(triples, vec![(3, 4, 5), (6, 8, 10)]);
55//! ```
56//!
57//! Error handling with [`Result`]:
58//!
59//! ```
60//! use fp_library::{brands::*, functions::*};
61//! use fp_macros::m_do;
62//!
63//! fn parse_field(input: &str) -> Result<i32, String> {
64//! 	input.parse().map_err(|_| format!("invalid: {}", input))
65//! }
66//!
67//! let result: Result<i32, String> = m_do!(ResultErrAppliedBrand<String> {
68//! 	x <- parse_field("10");
69//! 	y <- parse_field("20");
70//! 	let sum = x + y;
71//! 	pure(sum)
72//! });
73//! assert_eq!(result, Ok(30));
74//!
75//! let result: Result<i32, String> = m_do!(ResultErrAppliedBrand<String> {
76//! 	x <- parse_field("10");
77//! 	y <- parse_field("abc");
78//! 	pure(x + y)
79//! });
80//! assert_eq!(result, Err("invalid: abc".to_string()));
81//! ```
82//!
83//! The `m_do!` macro supports typed bindings, let bindings, sequencing, and
84//! automatic `pure` rewriting:
85//!
86//! ```
87//! use fp_library::{brands::*, functions::*};
88//! use fp_macros::m_do;
89//!
90//! // Typed bindings
91//! let r = m_do!(OptionBrand { x: i32 <- Some(5); pure(x * 2) });
92//! assert_eq!(r, Some(10));
93//!
94//! // Let bindings for pure local computations
95//! let r = m_do!(OptionBrand {
96//! 	x <- Some(5);
97//! 	let y = x * 2;
98//! 	pure(y)
99//! });
100//! assert_eq!(r, Some(10));
101//!
102//! // Sequencing: execute for effects, discard result
103//! let r = m_do!(OptionBrand { Some(()); pure(42) });
104//! assert_eq!(r, Some(42));
105//!
106//! // `pure(...)` is auto-rewritten with the correct brand
107//! let r = m_do!(OptionBrand {
108//! 	x <- Some(5);
109//! 	y <- pure(x + 1);
110//! 	pure(x + y)
111//! });
112//! assert_eq!(r, Some(11));
113//! ```
114
115#[fp_macros::document_module]
116mod inner {
117	use {
118		crate::{
119			classes::*,
120			kinds::*,
121		},
122		fp_macros::*,
123	};
124
125	/// A type class for monads, allowing for sequencing computations where the
126	/// structure of the computation depends on the result of the previous
127	/// computation.
128	///
129	/// `class (Applicative m, Semimonad m) => Monad m`
130	///
131	/// A lawful `Monad` must satisfy three laws:
132	///
133	/// 1. **Left identity**: `bind(pure(a), f) ≡ f(a)`: lifting a value and
134	///    immediately binding it is the same as applying the function directly.
135	/// 2. **Right identity**: `bind(m, pure) ≡ m`: binding a computation to
136	///    `pure` leaves it unchanged.
137	/// 3. **Associativity**: `bind(bind(m, f), g) ≡ bind(m, |x| bind(f(x), g))`:
138	///    the order of nesting doesn't matter, only the order of operations.
139	#[document_examples]
140	///
141	/// Monad laws for [`Option`]:
142	///
143	/// ```
144	/// use fp_library::{brands::*, functions::*};
145	/// use fp_macros::m_do;
146	///
147	/// let f = |x: i32| Some(x + 1);
148	/// let g = |x: i32| Some(x * 2);
149	///
150	/// // Left identity: bind(pure(a), f) ≡ f(a)
151	/// assert_eq!(
152	/// 	bind::<OptionBrand, _, _>(pure::<OptionBrand, _>(5), f),
153	/// 	f(5),
154	/// );
155	/// // With m_do!: wrapping in pure then binding is the same as calling f
156	/// assert_eq!(
157	/// 	m_do!(OptionBrand { x <- pure(5); pure(x + 1) }),
158	/// 	Some(6),
159	/// );
160	///
161	/// // Right identity: bind(m, pure) ≡ m
162	/// assert_eq!(
163	/// 	bind::<OptionBrand, _, _>(Some(42), pure::<OptionBrand, _>),
164	/// 	Some(42),
165	/// );
166	/// // With m_do!: extracting and re-wrapping is a no-op
167	/// assert_eq!(
168	/// 	m_do!(OptionBrand { x <- Some(42); pure(x) }),
169	/// 	Some(42),
170	/// );
171	///
172	/// // Associativity: bind(bind(m, f), g) ≡ bind(m, |x| bind(f(x), g))
173	/// assert_eq!(
174	/// 	bind::<OptionBrand, _, _>(
175	/// 		bind::<OptionBrand, _, _>(Some(5), f),
176	/// 		g,
177	/// 	),
178	/// 	bind::<OptionBrand, _, _>(Some(5), |x| bind::<OptionBrand, _, _>(f(x), g)),
179	/// );
180	/// // With m_do!: sequential binds compose naturally
181	/// assert_eq!(
182	/// 	m_do!(OptionBrand { x <- Some(5); y <- pure(x + 1); pure(y * 2) }),
183	/// 	Some(12),
184	/// );
185	/// ```
186	///
187	/// Monad laws for [`Vec`]:
188	///
189	/// ```
190	/// use fp_library::{
191	/// 	brands::*,
192	/// 	functions::*,
193	/// };
194	///
195	/// let f = |x: i32| vec![x, x + 1];
196	/// let g = |x: i32| vec![x * 10];
197	///
198	/// // Left identity: bind(pure(a), f) ≡ f(a)
199	/// assert_eq!(bind::<VecBrand, _, _>(pure::<VecBrand, _>(3), f), f(3),);
200	///
201	/// // Right identity: bind(m, pure) ≡ m
202	/// assert_eq!(bind::<VecBrand, _, _>(vec![1, 2, 3], pure::<VecBrand, _>), vec![1, 2, 3],);
203	///
204	/// // Associativity: bind(bind(m, f), g) ≡ bind(m, |x| bind(f(x), g))
205	/// let m = vec![1, 2];
206	/// assert_eq!(
207	/// 	bind::<VecBrand, _, _>(bind::<VecBrand, _, _>(m.clone(), f), g,),
208	/// 	bind::<VecBrand, _, _>(m, |x| bind::<VecBrand, _, _>(f(x), g)),
209	/// );
210	/// ```
211	pub trait Monad: Applicative + Semimonad {}
212
213	/// Blanket implementation of [`Monad`].
214	#[document_type_parameters("The brand type.")]
215	impl<Brand> Monad for Brand where Brand: Applicative + Semimonad {}
216
217	/// Executes a monadic action conditionally.
218	///
219	/// Evaluates the monadic boolean condition, then returns one of the two branches
220	/// depending on the result. Both branches are provided as monadic values.
221	#[document_signature]
222	///
223	#[document_type_parameters(
224		"The lifetime of the computations.",
225		"The brand of the monad.",
226		"The type of the value produced by each branch."
227	)]
228	///
229	#[document_parameters(
230		"A monadic computation that produces a boolean.",
231		"The computation to execute if the condition is `true`.",
232		"The computation to execute if the condition is `false`."
233	)]
234	///
235	#[document_returns("The result of the selected branch.")]
236	#[document_examples]
237	///
238	/// ```
239	/// use fp_library::{
240	/// 	brands::*,
241	/// 	functions::*,
242	/// };
243	///
244	/// let result = if_m::<OptionBrand, _>(Some(true), Some(1), Some(0));
245	/// assert_eq!(result, Some(1));
246	///
247	/// let result = if_m::<OptionBrand, _>(Some(false), Some(1), Some(0));
248	/// assert_eq!(result, Some(0));
249	///
250	/// let result = if_m::<OptionBrand, i32>(None, Some(1), Some(0));
251	/// assert_eq!(result, None);
252	/// ```
253	pub fn if_m<'a, Brand: Monad, A: 'a>(
254		cond: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, bool>),
255		then_branch: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
256		else_branch: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
257	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
258	where
259		Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): Clone, {
260		Brand::bind(cond, move |c| if c { then_branch.clone() } else { else_branch.clone() })
261	}
262
263	/// Performs a monadic action when a monadic condition is true.
264	///
265	/// Evaluates the monadic boolean condition, then executes the action if the
266	/// result is `true`, otherwise returns `pure(())`.
267	#[document_signature]
268	///
269	#[document_type_parameters("The lifetime of the computations.", "The brand of the monad.")]
270	///
271	#[document_parameters(
272		"A monadic computation that produces a boolean.",
273		"The action to perform if the condition is true."
274	)]
275	///
276	#[document_returns("The action if the condition is true, otherwise `pure(())`.")]
277	#[document_examples]
278	///
279	/// ```
280	/// use fp_library::{
281	/// 	brands::*,
282	/// 	functions::*,
283	/// };
284	///
285	/// assert_eq!(when_m::<OptionBrand>(Some(true), Some(())), Some(()));
286	/// assert_eq!(when_m::<OptionBrand>(Some(false), Some(())), Some(()));
287	/// assert_eq!(when_m::<OptionBrand>(None, Some(())), None);
288	/// ```
289	pub fn when_m<'a, Brand: Monad>(
290		cond: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, bool>),
291		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
292	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>)
293	where
294		Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>): Clone, {
295		Brand::bind(cond, move |c| if c { action.clone() } else { Brand::pure(()) })
296	}
297
298	/// Performs a monadic action unless a monadic condition is true.
299	///
300	/// Evaluates the monadic boolean condition, then executes the action if the
301	/// result is `false`, otherwise returns `pure(())`.
302	#[document_signature]
303	///
304	#[document_type_parameters("The lifetime of the computations.", "The brand of the monad.")]
305	///
306	#[document_parameters(
307		"A monadic computation that produces a boolean.",
308		"The action to perform if the condition is false."
309	)]
310	///
311	#[document_returns("The action if the condition is false, otherwise `pure(())`.")]
312	#[document_examples]
313	///
314	/// ```
315	/// use fp_library::{
316	/// 	brands::*,
317	/// 	functions::*,
318	/// };
319	///
320	/// assert_eq!(unless_m::<OptionBrand>(Some(false), Some(())), Some(()));
321	/// assert_eq!(unless_m::<OptionBrand>(Some(true), Some(())), Some(()));
322	/// assert_eq!(unless_m::<OptionBrand>(None, Some(())), None);
323	/// ```
324	pub fn unless_m<'a, Brand: Monad>(
325		cond: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, bool>),
326		action: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>),
327	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>)
328	where
329		Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>): Clone, {
330		Brand::bind(cond, move |c| if !c { action.clone() } else { Brand::pure(()) })
331	}
332}
333
334pub use inner::*;