Skip to main content

fp_library/classes/
monad_plus.rs

1//! Monads with a choice operator and identity element, combining [`Monad`](crate::classes::Monad) and [`Alternative`](crate::classes::Alternative).
2//!
3//! A `MonadPlus` is a type that supports both monadic sequencing
4//! ([`Monad`](crate::classes::Monad)) and choice with an identity element
5//! ([`Alternative`](crate::classes::Alternative)). It has no members of its own; it specifies that
6//! the type constructor has both `Monad` and `Alternative` instances.
7//!
8//! This module is a port of PureScript's
9//! [`Control.MonadPlus`](https://pursuit.purescript.org/packages/purescript-control/docs/Control.MonadPlus).
10//!
11//! # Hierarchy
12//!
13//! ```text
14//! Functor
15//!   |
16//!   +-- Pointed + Semiapplicative --> Applicative
17//!   |
18//!   +-- Applicative + Semimonad ----> Monad
19//!   |
20//!   +-- Alt + (identity element) ---> Plus
21//!   |
22//!   +-- Applicative + Plus ---------> Alternative
23//!   |
24//!   +-- Monad + Alternative --------> MonadPlus
25//! ```
26
27#[fp_macros::document_module(no_validation)]
28mod inner {
29	use crate::classes::*;
30
31	/// A type class for monads that also support choice, combining [`Monad`]
32	/// and [`Alternative`].
33	///
34	/// `class (Monad m, Alternative m) => MonadPlus m`
35	///
36	/// `MonadPlus` has no members of its own. It specifies that the type
37	/// constructor supports both monadic sequencing (`bind`, `pure`) and
38	/// choice with an identity element (`alt`, `empty`).
39	///
40	/// # Laws
41	///
42	/// A lawful `MonadPlus` must satisfy:
43	///
44	/// - **Distributivity:** binding over a choice distributes:
45	///
46	///   ```text
47	///   bind(alt(x, y), f) == alt(bind(x, f), bind(y, f))
48	///   ```
49	///
50	/// The following property also holds for any `Monad + Alternative`:
51	///
52	/// - **Left zero:** binding on the identity element yields the
53	///   identity element:
54	///
55	///   ```text
56	///   bind(empty(), f) == empty()
57	///   ```
58	///
59	/// # Examples
60	///
61	/// Distributivity law for [`Vec`]:
62	///
63	/// ```
64	/// use fp_library::{
65	/// 	brands::*,
66	/// 	functions::explicit::*,
67	/// };
68	///
69	/// let x: Vec<i32> = vec![1, 2];
70	/// let y: Vec<i32> = vec![3, 4];
71	/// let f = |n: i32| vec![n * 10, n * 100];
72	///
73	/// assert_eq!(
74	/// 	bind::<VecBrand, _, _, _, _>(alt::<VecBrand, _, _, _>(x.clone(), y.clone()), f),
75	/// 	alt::<VecBrand, _, _, _>(
76	/// 		bind::<VecBrand, _, _, _, _>(x, f),
77	/// 		bind::<VecBrand, _, _, _, _>(y, f)
78	/// 	),
79	/// );
80	/// ```
81	///
82	/// Left-zero law for [`Vec`]:
83	///
84	/// ```
85	/// use fp_library::{
86	/// 	brands::*,
87	/// 	functions::{
88	/// 		explicit::bind,
89	/// 		*,
90	/// 	},
91	/// };
92	///
93	/// let f = |n: i32| vec![n * 2];
94	///
95	/// assert_eq!(
96	/// 	bind::<VecBrand, _, _, _, _>(plus_empty::<VecBrand, i32>(), f),
97	/// 	plus_empty::<VecBrand, i32>(),
98	/// );
99	/// ```
100	///
101	/// # Implementors
102	///
103	/// The blanket implementation applies to every brand with both [`Monad`]
104	/// and [`Alternative`]. The following brands are known to be *lawful*
105	/// (satisfying distributivity and left zero):
106	///
107	/// - [`VecBrand`](crate::brands::VecBrand)
108	/// - [`CatListBrand`](crate::brands::CatListBrand)
109	///
110	/// Note that [`OptionBrand`](crate::brands::OptionBrand) acquires the
111	/// trait via the blanket impl but does *not* satisfy the distributivity
112	/// law, because `alt` for `Option` picks the first `Some` and discards
113	/// the second branch entirely.
114	pub trait MonadPlus: Monad + Alternative {}
115
116	/// Blanket implementation of [`MonadPlus`].
117	#[document_type_parameters("The brand type.")]
118	impl<Brand> MonadPlus for Brand where Brand: Monad + Alternative {}
119}
120
121pub use inner::*;
122
123#[cfg(test)]
124mod tests {
125	use {
126		crate::{
127			brands::*,
128			functions::{
129				explicit::{
130					alt,
131					bind,
132				},
133				*,
134			},
135			types::cat_list::CatList,
136		},
137		quickcheck_macros::quickcheck,
138	};
139
140	// -- Distributivity: bind(alt(x, y), f) == alt(bind(x, f), bind(y, f)) --
141
142	/// Tests the distributivity law for MonadPlus with VecBrand.
143	#[quickcheck]
144	fn distributivity_vec(
145		x: Vec<i32>,
146		y: Vec<i32>,
147	) -> bool {
148		let f = |n: i32| {
149			if n > 0 { vec![n.wrapping_mul(2)] } else { vec![] }
150		};
151		bind::<VecBrand, _, _, _, _>(alt::<VecBrand, _, _, _>(x.clone(), y.clone()), f)
152			== alt::<VecBrand, _, _, _>(
153				bind::<VecBrand, _, _, _, _>(x, f),
154				bind::<VecBrand, _, _, _, _>(y, f),
155			)
156	}
157
158	/// Tests the distributivity law for MonadPlus with CatListBrand.
159	#[quickcheck]
160	fn distributivity_cat_list(
161		xv: Vec<i32>,
162		yv: Vec<i32>,
163	) -> bool {
164		let x: CatList<i32> = xv.into_iter().collect();
165		let y: CatList<i32> = yv.into_iter().collect();
166		let f = |n: i32| -> CatList<i32> {
167			if n > 0 { CatList::singleton(n.wrapping_mul(2)) } else { CatList::empty() }
168		};
169		bind::<CatListBrand, _, _, _, _>(alt::<CatListBrand, _, _, _>(x.clone(), y.clone()), f)
170			== alt::<CatListBrand, _, _, _>(
171				bind::<CatListBrand, _, _, _, _>(x, f),
172				bind::<CatListBrand, _, _, _, _>(y, f),
173			)
174	}
175
176	// -- Left zero: bind(empty(), f) == empty() --
177
178	/// Tests the left-zero law for MonadPlus with OptionBrand.
179	#[test]
180	fn left_zero_option() {
181		let f = |n: i32| if n > 0 { Some(n * 2) } else { None };
182		assert_eq!(
183			bind::<OptionBrand, _, _, _, _>(plus_empty::<OptionBrand, i32>(), f),
184			plus_empty::<OptionBrand, i32>(),
185		);
186	}
187
188	/// Tests the left-zero law for MonadPlus with VecBrand.
189	#[test]
190	fn left_zero_vec() {
191		let f = |n: i32| if n > 0 { vec![n * 2] } else { vec![] };
192		assert_eq!(
193			bind::<VecBrand, _, _, _, _>(plus_empty::<VecBrand, i32>(), f),
194			plus_empty::<VecBrand, i32>(),
195		);
196	}
197
198	/// Tests the left-zero law for MonadPlus with CatListBrand.
199	#[test]
200	fn left_zero_cat_list() {
201		let f = |n: i32| -> CatList<i32> {
202			if n > 0 { CatList::singleton(n * 2) } else { CatList::empty() }
203		};
204		assert_eq!(
205			bind::<CatListBrand, _, _, _, _>(plus_empty::<CatListBrand, i32>(), f),
206			plus_empty::<CatListBrand, i32>(),
207		);
208	}
209}