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}