Skip to main content

fp_library/classes/
monad_rec.rs

1//! Monads that support stack-safe tail recursion via [`ControlFlow`](core::ops::ControlFlow).
2//!
3//! ### Examples
4//!
5//! ```
6//! use {
7//! 	core::ops::ControlFlow,
8//! 	fp_library::{
9//! 		brands::*,
10//! 		classes::*,
11//! 		functions::tail_rec_m,
12//! 		types::*,
13//! 	},
14//! };
15//!
16//! // A tail-recursive function to calculate factorial
17//! fn factorial(n: u64) -> Thunk<'static, u64> {
18//! 	tail_rec_m::<ThunkBrand, _, _>(
19//! 		|(n, acc)| {
20//! 			if n == 0 {
21//! 				Thunk::pure(ControlFlow::Break(acc))
22//! 			} else {
23//! 				Thunk::pure(ControlFlow::Continue((n - 1, n * acc)))
24//! 			}
25//! 		},
26//! 		(n, 1),
27//! 	)
28//! }
29//!
30//! assert_eq!(factorial(5).evaluate(), 120);
31//! ```
32
33#[fp_macros::document_module]
34mod inner {
35	use {
36		crate::{
37			classes::*,
38			kinds::*,
39		},
40		core::ops::ControlFlow,
41		fp_macros::*,
42	};
43
44	/// A type class for monads that support stack-safe tail recursion.
45	///
46	/// ### Important Design Note
47	///
48	/// [`Thunk<'a, A>`](crate::types::Thunk) CAN implement this trait (HKT-compatible).
49	/// [`Trampoline<A>`](crate::types::Trampoline) CANNOT implement this trait (requires `'static`).
50	///
51	/// `Thunk`'s `tail_rec_m` implementation uses a loop and is stack-safe.
52	/// However, `Thunk`'s `bind` chains are NOT stack-safe.
53	/// `Trampoline` is stack-safe for both `tail_rec_m` and `bind` chains.
54	///
55	/// ### Laws
56	///
57	/// 1. **Identity**: `tail_rec_m(|a| pure(ControlFlow::Break(a)), x) == pure(x)`.
58	///    Immediately wrapping a value in [`ControlFlow::Break`] must be equivalent
59	///    to [`pure`](crate::classes::Pointed::pure).
60	///
61	/// 2. **Equivalence/Unfolding**: `tail_rec_m(f, a)` is equivalent to
62	///    `f(a) >>= match { Continue(a') => tail_rec_m(f, a'), Break(b) => pure(b) }`.
63	///    That is, `tail_rec_m` must produce the same result as manually stepping
64	///    through the recursion with `bind`, but without consuming stack space.
65	///
66	/// ### Caveats
67	///
68	/// For multi-element containers ([`VecBrand`](crate::brands::VecBrand),
69	/// [`CatListBrand`](crate::brands::CatListBrand)), if the step function always
70	/// produces [`ControlFlow::Continue`] values, the computation never terminates
71	/// and consumes unbounded memory. Single-element containers
72	/// ([`ThunkBrand`](crate::brands::ThunkBrand),
73	/// [`IdentityBrand`](crate::brands::IdentityBrand), etc.) do not have this
74	/// issue because they process exactly one element per iteration.
75	///
76	/// ### Class Invariant
77	///
78	/// [`tail_rec_m`](MonadRec::tail_rec_m) must execute in constant stack space
79	/// regardless of how many [`ControlFlow::Continue`] iterations occur. This is
80	/// a structural requirement on the implementation, not an algebraic law.
81	///
82	/// ### Examples
83	///
84	/// Demonstrating the identity law with [`OptionBrand`](crate::brands::OptionBrand):
85	///
86	/// ```
87	/// use {
88	/// 	core::ops::ControlFlow,
89	/// 	fp_library::{
90	/// 		brands::*,
91	/// 		functions::*,
92	/// 	},
93	/// };
94	///
95	/// // Identity law: tail_rec_m(|a| pure(ControlFlow::Break(a)), x) == pure(x)
96	/// let result = tail_rec_m::<OptionBrand, _, _>(|a| Some(ControlFlow::Break(a)), 42);
97	/// assert_eq!(result, Some(42));
98	/// ```
99	pub trait MonadRec: Monad {
100		/// Performs tail-recursive monadic computation.
101		#[document_signature]
102		///
103		#[document_type_parameters(
104			"The lifetime of the computation.",
105			"The type of the initial value and loop state.",
106			"The type of the result."
107		)]
108		///
109		#[document_parameters("The step function.", "The initial value.")]
110		///
111		#[document_returns("The result of the computation.")]
112		#[document_examples]
113		///
114		/// ```
115		/// use {
116		/// 	core::ops::ControlFlow,
117		/// 	fp_library::{
118		/// 		brands::*,
119		/// 		functions::*,
120		/// 		types::*,
121		/// 	},
122		/// };
123		///
124		/// let result = tail_rec_m::<ThunkBrand, _, _>(
125		/// 	|n| {
126		/// 		if n < 10 {
127		/// 			Thunk::pure(ControlFlow::Continue(n + 1))
128		/// 		} else {
129		/// 			Thunk::pure(ControlFlow::Break(n))
130		/// 		}
131		/// 	},
132		/// 	0,
133		/// );
134		///
135		/// assert_eq!(result.evaluate(), 10);
136		/// ```
137		fn tail_rec_m<'a, A: 'a, B: 'a>(
138			func: impl Fn(
139				A,
140			)
141				-> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ControlFlow<B, A>>)
142			+ 'a,
143			initial: A,
144		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
145	}
146
147	/// Performs tail-recursive monadic computation.
148	///
149	/// Free function version that dispatches to [the type class' associated function][`MonadRec::tail_rec_m`].
150	#[document_signature]
151	///
152	#[document_type_parameters(
153		"The lifetime of the computation.",
154		"The brand of the monad.",
155		"The type of the initial value and loop state.",
156		"The type of the result."
157	)]
158	///
159	#[document_parameters("The step function.", "The initial value.")]
160	///
161	#[document_returns("The result of the computation.")]
162	#[document_examples]
163	///
164	/// ```
165	/// use {
166	/// 	core::ops::ControlFlow,
167	/// 	fp_library::{
168	/// 		brands::*,
169	/// 		functions::*,
170	/// 		types::*,
171	/// 	},
172	/// };
173	///
174	/// let result = tail_rec_m::<ThunkBrand, _, _>(
175	/// 	|n| {
176	/// 		if n < 10 {
177	/// 			Thunk::pure(ControlFlow::Continue(n + 1))
178	/// 		} else {
179	/// 			Thunk::pure(ControlFlow::Break(n))
180	/// 		}
181	/// 	},
182	/// 	0,
183	/// );
184	///
185	/// assert_eq!(result.evaluate(), 10);
186	/// ```
187	pub fn tail_rec_m<'a, Brand: MonadRec, A: 'a, B: 'a>(
188		func: impl Fn(
189			A,
190		)
191			-> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ControlFlow<B, A>>)
192		+ 'a,
193		initial: A,
194	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
195		Brand::tail_rec_m(func, initial)
196	}
197
198	/// Runs a monadic action indefinitely.
199	///
200	/// Executes `action` in an infinite loop using [`tail_rec_m`] for stack safety.
201	/// The `action` parameter is a closure that produces the monadic action, since
202	/// the action must be called repeatedly and cannot be consumed. The return type
203	/// `B` is never instantiated: for non-terminating monads like
204	/// [`ThunkBrand`](crate::brands::ThunkBrand) the computation diverges, while
205	/// for collection monads like [`VecBrand`](crate::brands::VecBrand) it produces
206	/// an empty result.
207	///
208	/// This function is stack-safe via [`tail_rec_m`].
209	#[document_signature]
210	///
211	#[document_type_parameters(
212		"The lifetime of the computation.",
213		"The brand of the monad.",
214		"The type of the value produced by the action (discarded each iteration).",
215		"The return type (never instantiated for non-terminating monads)."
216	)]
217	///
218	#[document_parameters("A closure that produces the monadic action to run each iteration.")]
219	///
220	#[document_returns(
221		"A monadic value that never terminates. For `Option`, returns `None` immediately since mapping over `None` short-circuits."
222	)]
223	#[document_examples]
224	///
225	/// For `OptionBrand`, `forever` returns `None` immediately because
226	/// `map` over `None` short-circuits:
227	///
228	/// ```
229	/// use fp_library::{
230	/// 	brands::*,
231	/// 	functions::*,
232	/// };
233	///
234	/// let result: Option<String> = forever::<OptionBrand, _, _>(|| None::<i32>);
235	/// assert_eq!(result, None);
236	/// ```
237	///
238	/// **Warning:** For non-short-circuiting monads like `Vec` or `Thunk`,
239	/// `forever` genuinely runs forever and will not terminate.
240	pub fn forever<'a, Brand: MonadRec, A: 'a, B: 'a>(
241		action: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) + 'a
242	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
243		Brand::tail_rec_m(move |()| Brand::map(|_| ControlFlow::Continue(()), action()), ())
244	}
245
246	/// Repeatedly runs a monadic action, accumulating results while it returns [`Some`].
247	///
248	/// Executes `action` in a loop. When `action` returns `Some(a)`, the value `a`
249	/// is accumulated via [`Semigroup::append`](crate::classes::Semigroup::append).
250	/// When `action` returns `None`, the accumulated value is returned. The
251	/// accumulator starts at [`Monoid::empty()`](crate::classes::Monoid::empty).
252	///
253	/// This function is stack-safe via [`tail_rec_m`].
254	#[document_signature]
255	///
256	#[document_type_parameters(
257		"The lifetime of the computation.",
258		"The brand of the monad.",
259		"The type of the accumulated value, which must implement [`Monoid`](crate::classes::Monoid) and [`Clone`]."
260	)]
261	///
262	#[document_parameters("A closure that produces a monadic `Option<A>` each iteration.")]
263	///
264	#[document_returns("The accumulated monoidal value once `action` returns `None`.")]
265	#[document_examples]
266	///
267	/// ```
268	/// use {
269	/// 	fp_library::{
270	/// 		brands::*,
271	/// 		functions::*,
272	/// 	},
273	/// 	std::cell::Cell,
274	/// };
275	///
276	/// // Accumulate strings until None
277	/// let items =
278	/// 	vec![Some("hello".to_string()), Some(" ".to_string()), Some("world".to_string()), None];
279	/// let idx = Cell::new(0usize);
280	/// let result = while_some::<OptionBrand, _>(|| {
281	/// 	let i = idx.get();
282	/// 	idx.set(i + 1);
283	/// 	Some(items[i].clone())
284	/// });
285	/// assert_eq!(result, Some("hello world".to_string()));
286	/// ```
287	pub fn while_some<'a, Brand: MonadRec, A: Monoid + Clone + 'a>(
288		action: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Option<A>>) + 'a
289	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
290		Brand::tail_rec_m(
291			move |acc: A| {
292				Brand::map(
293					move |opt: Option<A>| match opt {
294						None => ControlFlow::Break(acc.clone()),
295						Some(x) => ControlFlow::Continue(A::append(acc.clone(), x)),
296					},
297					action(),
298				)
299			},
300			A::empty(),
301		)
302	}
303
304	/// Repeatedly runs a monadic action until it returns [`Some`].
305	///
306	/// Executes `action` in a loop, discarding `None` results. As soon as `action`
307	/// returns `Some(x)`, the value `x` is returned.
308	///
309	/// This function is stack-safe via [`tail_rec_m`].
310	#[document_signature]
311	///
312	#[document_type_parameters(
313		"The lifetime of the computation.",
314		"The brand of the monad.",
315		"The type of the value inside the [`Option`]."
316	)]
317	///
318	#[document_parameters("A closure that produces a monadic `Option<A>` each iteration.")]
319	///
320	#[document_returns("The first `Some` value produced by `action`.")]
321	#[document_examples]
322	///
323	/// ```
324	/// use {
325	/// 	fp_library::{
326	/// 		brands::*,
327	/// 		functions::*,
328	/// 	},
329	/// 	std::cell::Cell,
330	/// };
331	///
332	/// // Returns None on first two calls, then Some(42)
333	/// let count = Cell::new(0usize);
334	/// let result = until_some::<OptionBrand, _>(|| {
335	/// 	count.set(count.get() + 1);
336	/// 	if count.get() < 3 { Some(None) } else { Some(Some(42)) }
337	/// });
338	/// assert_eq!(result, Some(42));
339	/// ```
340	pub fn until_some<'a, Brand: MonadRec, A: 'a>(
341		action: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Option<A>>) + 'a
342	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
343		Brand::tail_rec_m(
344			move |()| {
345				Brand::map(
346					|opt| match opt {
347						None => ControlFlow::Continue(()),
348						Some(x) => ControlFlow::Break(x),
349					},
350					action(),
351				)
352			},
353			(),
354		)
355	}
356
357	/// Applies a monadic step function exactly `n` times, threading state through each iteration.
358	///
359	/// Starting from `initial`, applies `f` to the current state `n` times, returning
360	/// the final state. When `n` is 0, returns `pure(initial)` immediately.
361	///
362	/// This function is stack-safe via [`tail_rec_m`].
363	#[document_signature]
364	///
365	#[document_type_parameters(
366		"The lifetime of the computation.",
367		"The brand of the monad.",
368		"The type of the state threaded through each iteration."
369	)]
370	///
371	#[document_parameters(
372		"The number of times to apply the step function.",
373		"The monadic step function applied to the current state each iteration.",
374		"The initial state."
375	)]
376	///
377	#[document_returns("The state after applying `f` exactly `n` times.")]
378	#[document_examples]
379	///
380	/// ```
381	/// use fp_library::{
382	/// 	brands::*,
383	/// 	functions::*,
384	/// };
385	///
386	/// // Increment 5 times starting from 0
387	/// let result = repeat_m::<OptionBrand, _>(5, |s| Some(s + 1), 0);
388	/// assert_eq!(result, Some(5));
389	///
390	/// // Zero repetitions returns the initial state
391	/// let result = repeat_m::<OptionBrand, _>(0, |s: i32| Some(s + 1), 10);
392	/// assert_eq!(result, Some(10));
393	/// ```
394	pub fn repeat_m<'a, Brand: MonadRec, S: 'a>(
395		n: usize,
396		f: impl Fn(S) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, S>) + 'a,
397		initial: S,
398	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, S>) {
399		Brand::tail_rec_m(
400			move |(remaining, state): (usize, S)| {
401				if remaining == 0 {
402					Brand::pure(ControlFlow::Break(state))
403				} else {
404					Brand::map(
405						move |new_state| ControlFlow::Continue((remaining - 1, new_state)),
406						f(state),
407					)
408				}
409			},
410			(n, initial),
411		)
412	}
413
414	/// Runs a monadic body as long as a monadic condition returns `true`.
415	///
416	/// Evaluates `condition` each iteration. If it returns `true`, executes `body`
417	/// and loops. If it returns `false`, the loop terminates with `()`.
418	///
419	/// This function is stack-safe via [`tail_rec_m`].
420	#[document_signature]
421	///
422	#[document_type_parameters("The lifetime of the computation.", "The brand of the monad.")]
423	///
424	#[document_parameters(
425		"A closure that produces a monadic `bool` each iteration.",
426		"A closure that produces the monadic body to execute when the condition is `true`."
427	)]
428	///
429	#[document_returns("`()` wrapped in the monad once the condition returns `false`.")]
430	#[document_examples]
431	///
432	/// ```
433	/// use {
434	/// 	fp_library::{
435	/// 		brands::*,
436	/// 		functions::*,
437	/// 	},
438	/// 	std::cell::Cell,
439	/// };
440	///
441	/// let counter = Cell::new(0usize);
442	/// let result = while_m::<OptionBrand>(
443	/// 	|| Some(counter.get() < 5),
444	/// 	|| {
445	/// 		counter.set(counter.get() + 1);
446	/// 		Some(())
447	/// 	},
448	/// );
449	/// assert_eq!(result, Some(()));
450	/// assert_eq!(counter.get(), 5);
451	/// ```
452	pub fn while_m<'a, Brand: MonadRec>(
453		condition: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, bool>) + 'a,
454		body: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) + 'a,
455	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
456		Brand::tail_rec_m(
457			move |check_cond: bool| {
458				if check_cond {
459					Brand::map(
460						|cond| {
461							if cond { ControlFlow::Continue(false) } else { ControlFlow::Break(()) }
462						},
463						condition(),
464					)
465				} else {
466					Brand::map(|()| ControlFlow::Continue(true), body())
467				}
468			},
469			true,
470		)
471	}
472
473	/// Runs a monadic body until a monadic condition returns `true`.
474	///
475	/// Executes `body`, then evaluates `condition`. If the condition returns `false`,
476	/// loops again. If it returns `true`, the loop terminates with `()`.
477	///
478	/// This function is stack-safe via [`tail_rec_m`].
479	#[document_signature]
480	///
481	#[document_type_parameters("The lifetime of the computation.", "The brand of the monad.")]
482	///
483	#[document_parameters(
484		"A closure that produces a monadic `bool` each iteration.",
485		"A closure that produces the monadic body to execute each iteration."
486	)]
487	///
488	#[document_returns("`()` wrapped in the monad once the condition returns `true`.")]
489	#[document_examples]
490	///
491	/// ```
492	/// use {
493	/// 	fp_library::{
494	/// 		brands::*,
495	/// 		functions::*,
496	/// 	},
497	/// 	std::cell::Cell,
498	/// };
499	///
500	/// let counter = Cell::new(0usize);
501	/// let result = until_m::<OptionBrand>(
502	/// 	|| Some(counter.get() >= 5),
503	/// 	|| {
504	/// 		counter.set(counter.get() + 1);
505	/// 		Some(())
506	/// 	},
507	/// );
508	/// assert_eq!(result, Some(()));
509	/// assert_eq!(counter.get(), 5);
510	/// ```
511	pub fn until_m<'a, Brand: MonadRec>(
512		condition: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, bool>) + 'a,
513		body: impl Fn() -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) + 'a,
514	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ()>) {
515		Brand::tail_rec_m(
516			move |run_body: bool| {
517				if run_body {
518					Brand::map(|()| ControlFlow::Continue(false), body())
519				} else {
520					Brand::map(
521						|cond| {
522							if cond { ControlFlow::Break(()) } else { ControlFlow::Continue(true) }
523						},
524						condition(),
525					)
526				}
527			},
528			true,
529		)
530	}
531}
532
533pub use inner::*;
534
535#[cfg(test)]
536#[expect(
537	clippy::indexing_slicing,
538	reason = "Tests use panicking operations for brevity and clarity"
539)]
540mod tests {
541	use {
542		crate::{
543			brands::*,
544			functions::*,
545			types::*,
546		},
547		core::ops::ControlFlow,
548		quickcheck_macros::quickcheck,
549		std::cell::Cell,
550	};
551
552	/// MonadRec identity law for OptionBrand: tail_rec_m(|a| pure(Break(a)), x) == pure(x).
553	#[quickcheck]
554	fn prop_monad_rec_identity_option(x: i32) -> bool {
555		let result = tail_rec_m::<OptionBrand, _, _>(|a| Some(ControlFlow::Break(a)), x);
556		result == Some(x)
557	}
558
559	/// MonadRec identity law for ThunkBrand: tail_rec_m(|a| pure(Break(a)), x) == pure(x).
560	#[quickcheck]
561	fn prop_monad_rec_identity_thunk(x: i32) -> bool {
562		let result = tail_rec_m::<ThunkBrand, _, _>(|a| Thunk::pure(ControlFlow::Break(a)), x);
563		result.evaluate() == pure::<ThunkBrand, _>(x).evaluate()
564	}
565
566	/// `forever` with OptionBrand returns None immediately (short-circuits).
567	#[test]
568	fn test_forever_option_none() {
569		let result: Option<String> = forever::<OptionBrand, _, _>(|| None::<i32>);
570		assert_eq!(result, None);
571	}
572
573	/// `forever` with ThunkBrand is stack-safe over 100k iterations.
574	#[test]
575	fn test_forever_thunk_stack_safe() {
576		use std::cell::Cell;
577
578		let counter = Cell::new(0usize);
579		let limit = 100_000usize;
580
581		// Use OptionBrand: forever returns None immediately since
582		// map over None produces None (the loop never actually runs).
583		// Instead, test with a Thunk-based approach: use tail_rec_m
584		// directly to verify the forever pattern is stack-safe.
585		let result = tail_rec_m::<ThunkBrand, _, _>(
586			|()| {
587				counter.set(counter.get() + 1);
588				if counter.get() >= limit {
589					Thunk::pure(ControlFlow::Break(counter.get()))
590				} else {
591					Thunk::pure(ControlFlow::Continue(()))
592				}
593			},
594			(),
595		);
596		assert_eq!(result.evaluate(), limit);
597	}
598
599	/// `while_some` accumulates values until None.
600	#[test]
601	fn test_while_some_option() {
602		let items =
603			[Some("hello".to_string()), Some(" ".to_string()), Some("world".to_string()), None];
604		let idx = Cell::new(0usize);
605		let result = while_some::<OptionBrand, _>(|| {
606			let i = idx.get();
607			idx.set(i + 1);
608			Some(items[i].clone())
609		});
610		assert_eq!(result, Some("hello world".to_string()));
611	}
612
613	/// `while_some` with immediate None returns empty.
614	#[test]
615	fn test_while_some_immediate_none() {
616		let result = while_some::<OptionBrand, String>(|| Some(None));
617		assert_eq!(result, Some(String::new()));
618	}
619
620	/// `until_some` returns the first Some value.
621	#[test]
622	fn test_until_some_option() {
623		let counter = Cell::new(0usize);
624		let result = until_some::<OptionBrand, _>(|| {
625			let c = counter.get();
626			counter.set(c + 1);
627			if c < 3 { Some(None) } else { Some(Some(42)) }
628		});
629		assert_eq!(result, Some(42));
630	}
631
632	/// `until_some` returns immediately when action yields Some on first call.
633	#[test]
634	fn test_until_some_immediate() {
635		let result = until_some::<OptionBrand, _>(|| Some(Some(99)));
636		assert_eq!(result, Some(99));
637	}
638
639	/// `repeat_m` applies the step function n times.
640	#[test]
641	fn test_repeat_m_option() {
642		let result = repeat_m::<OptionBrand, _>(5, |s| Some(s + 1), 0);
643		assert_eq!(result, Some(5));
644	}
645
646	/// `repeat_m` with zero repetitions returns the initial state.
647	#[test]
648	fn test_repeat_m_zero() {
649		let result = repeat_m::<OptionBrand, _>(0, |s: i32| Some(s + 1), 10);
650		assert_eq!(result, Some(10));
651	}
652
653	/// `repeat_m` is stack-safe over 100k iterations.
654	#[test]
655	fn test_repeat_m_stack_safe() {
656		let result = repeat_m::<ThunkBrand, _>(100_000, |s| Thunk::pure(s + 1), 0usize);
657		assert_eq!(result.evaluate(), 100_000);
658	}
659
660	/// `while_m` runs body while condition is true.
661	#[test]
662	fn test_while_m_option() {
663		let counter = Cell::new(0usize);
664		let result = while_m::<OptionBrand>(
665			|| Some(counter.get() < 5),
666			|| {
667				counter.set(counter.get() + 1);
668				Some(())
669			},
670		);
671		assert_eq!(result, Some(()));
672		assert_eq!(counter.get(), 5);
673	}
674
675	/// `while_m` with initially false condition does not run body.
676	#[test]
677	fn test_while_m_false_immediately() {
678		let counter = Cell::new(0usize);
679		let result = while_m::<OptionBrand>(
680			|| Some(false),
681			|| {
682				counter.set(counter.get() + 1);
683				Some(())
684			},
685		);
686		assert_eq!(result, Some(()));
687		assert_eq!(counter.get(), 0);
688	}
689
690	/// `until_m` runs body until condition is true.
691	#[test]
692	fn test_until_m_option() {
693		let counter = Cell::new(0usize);
694		let result = until_m::<OptionBrand>(
695			|| Some(counter.get() >= 5),
696			|| {
697				counter.set(counter.get() + 1);
698				Some(())
699			},
700		);
701		assert_eq!(result, Some(()));
702		assert_eq!(counter.get(), 5);
703	}
704
705	/// `until_m` always runs body at least once.
706	#[test]
707	fn test_until_m_runs_once() {
708		let counter = Cell::new(0usize);
709		let result = until_m::<OptionBrand>(
710			|| Some(true),
711			|| {
712				counter.set(counter.get() + 1);
713				Some(())
714			},
715		);
716		assert_eq!(result, Some(()));
717		assert_eq!(counter.get(), 1);
718	}
719}