A crate which defines and implements a complete set of Boolean functional combinators.
Yes, there are, notably Daniel Keep's boolinator, and
Rust's then()
and then_some()
methods.
boolinator
is a great crate and serves as an inspiration for this crate, as do some other sources.
However, boolinator
's provided set of combinators is not as complete as one might wish for (and,
relatively minor nit, nor are the combinator names as short as they could be. boolinator
's
interface is also stable (i.e. >v1.0.0) so this would be disruptive to address.)
In Rust's case, std
is conservative by design, and will only very slowly move toward a complete set of combinators for bool
.
My hope is that this crate can serve as a testing ground for both the naming and scope of boolean combinators to help inform the Rust lang team's discussion and decisions with usage data and feedback.
Well, because bool
is a very versatile data type! For example, adding to a collection only if the
item in question is not already present is a common operation. Map
datatypes often give you
this behavior by the nature of their design. Other containers such as Vec
, do not. So instead
of highly stateful, imperative code:
// ...
let mut found = false;
for needle in haystack {
if needle == item {
found = true;
break;
}
}
if !found {
haystack.push(item);
}
or imperative/declarative hybrid code:
// ...
if !haystack.contains(&item) {
haystack.push(item)
}
bool_ext
enables the following highly expressive, highly cohesive, declarative code:
// ...
haystack.contains(&item)
.or_do(|| haystack.push(item));
You are not alone! Debuggers have not yet caught up to fluent API design techniques and debugging fluent interfaces can indeed be objectively more work. On the other hand, proponents (such as me) will tell you that by elevating one's thinking from "micromanaging the CPU" to expressing one's intent by "shaping the data", far fewer bugs will be written in the first place, and the resulting code will be both more expressive and more maintainable (once the maintaining party has sufficient experience with this style of coding).
bool_ext
is implemented according to Bjarne Stroustrup's now classic definition of a zero
-overhead abstraction, where 1) you only pay for what you use and 2) you couldn't implement the
abstraction any better if you coded it yourself by hand.
Addressing 1), the bool_ext
create is very small, takes no dependencies, and most importantly
, any methods defined within the crate that you do not use are stripped out by the compiler
and are not a part of your resulting binary.
Regarding 2), each method is #[inline]
'd, adheres to the Single Responsibility Principle,
minimizes register pressure from inlining and when fully optimized (typically in release mode)
should compile down to exactly the same (or better) code that could be written by hand.
Up until v0.4.0, bool_ext
contained a _false()
variant for almost every method. Thanks to input
from izik1, and a lot of consideration, I decided that readability
didn't suffer when using boolean_condition().not().ok()
as opposed to
boolean_condition().ok_false()
or boolean_condition().or_ok()
. I do find
(!boolean_condition()).ok()
is significantly less readable (because of the required parentheses
and because the order of operations no longer proceeds exclusively left-to-right), but as izik1
pointed out, std::ops::Not
alleviates this. Thank you, izik1! :)
Licensed under either:
* MIT license (see LICENSE-MIT file)
* Apache License, Version 2.0 (see LICENSE-APACHE file)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.