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