Skip to main content

patterns/
select.rs

1use patterns_macros::because;
2
3pub fn either<T>(when: bool, yes: T, no: T) -> T {
4    if when {
5        yes
6    } else {
7        no
8    }
9}
10
11pub fn is_below<T: PartialOrd>(a: T, b: T) -> bool {
12    a < b
13}
14
15pub fn is_above<T: PartialOrd>(a: T, b: T) -> bool {
16    a > b
17}
18
19pub fn is_at_most<T: PartialOrd>(a: T, b: T) -> bool {
20    a <= b
21}
22
23pub fn is_at_least<T: PartialOrd>(a: T, b: T) -> bool {
24    a >= b
25}
26
27pub fn is_same<T: PartialEq>(a: T, b: T) -> bool {
28    a == b
29}
30because!(either, "the replacement for an if expression in a binding, so a choice between two values is a call and the branch cannot grow a body");
31because!(is_below, "a strict ordering test named so a comparison operator never appears in spec or app");
32because!(is_above, "the mirror of is_below, named rather than derived because negating a strict comparison flips to a non-strict one and that is a common error");
33because!(is_at_most, "the non-strict form of is_below, separate because the boundary case is exactly what a limit turns on");
34because!(is_at_least, "the non-strict form of is_above, separate because the boundary case is exactly what a limit turns on");
35because!(is_same, "equality as a call, so an equality operator never appears in spec or app");
36
37pub fn either_lazily<T>(when: bool, yes: fn() -> T, no: fn() -> T) -> T {
38    if when {
39        yes()
40    } else {
41        no()
42    }
43}
44because!(either_lazily, "the form that builds only the branch it returns, for a choice whose arms are expensive or whose unused arm would panic; either is eager and evaluates both arguments before the call");
45
46pub fn refuse_when<E, F: Clone>(checks: &[(bool, F)], ok: E) -> Result<E, F> {
47    for (refused, fault) in checks {
48        if *refused {
49            return Err(fault.clone());
50        }
51    }
52    Ok(ok)
53}
54because!(refuse_when, "a table of guards read top to bottom, the first true one refusing with its fault, so a decide arm with several rules is a list in the order the rules apply rather than a nest of either calls that reads inside out; every guard is built before the call, and refuse_when_lazily is the form for a guard that must not run until an earlier one has passed");
55
56pub type Guard<S, C, F> = (fn(&S, &C) -> bool, F);
57
58pub fn refuse_when_lazily<S, C, E, F: Clone>(
59    state: &S,
60    command: &C,
61    checks: &[Guard<S, C, F>],
62    ok: E,
63) -> Result<E, F> {
64    for (refused, fault) in checks {
65        if refused(state, command) {
66            return Err(fault.clone());
67        }
68    }
69    Ok(ok)
70}
71because!(refuse_when_lazily, "the guard table of refuse_when with each guard a function of the state and the command, run only until the first refusal, kept beside refuse_when because an eager table builds every guard before the call and a guard that is only safe to evaluate after an earlier one has refused cannot be written that way");
72
73pub fn flipped(value: bool) -> bool {
74    !value
75}
76because!(flipped, "a flag turned over, which a binding cannot write because the negation operator is control flow to the checker, and which the first setting that toggles needs before anything else in a project works");