Giter VIP home page Giter VIP logo

bool_ext's Introduction

bool_ext

A crate which defines and implements a complete set of Boolean functional combinators.

FAQ

Aren't there already crates like this?

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.

It's just a bool--why is the API surface so large?

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));

Should I use this? / I'm not sure about method-chaining/functional combinators

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.

What about negating a boolean?

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! :)

License

Licensed under either:

* MIT license (see LICENSE-MIT file)
* Apache License, Version 2.0 (see LICENSE-APACHE file)

at your option.

Contributions

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.

bool_ext's People

Contributors

u007d avatar xandkeeper avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

thealgorythm

bool_ext's Issues

`map` seems to be more like `map_or_else`

The current implementation of map seems to be nearly identical to Option<T>::map_or_else.

Hence I'd argue for said method to be renamed to map_or_else, and the addition of the missing map_or, map_or_default, unwrap, unwrap_or, unwrap_or_else, and unwrap_or_default functions- although the unwrap part of the last four names are up to bikeshedding, since bool isn't a container.

fn map_or<T, F: FnOnce() -> T>(&self, default: T, f: F) -> T;
fn map_or_default<T: Default, F: FnOnce() -> T>(&self, f: F) -> T;
fn unwrap<T>(&self, value: T) -> T; // panics if `self == false`
fn unwrap_or<T>(&self, if_true: T, if_false: T) -> T;
fn unwrap_or_else<T: Default, F: FnOnce() -> T>(&self, if_true: T, if_false: F) -> T;
fn unwrap_or_default<T: Default>(&self, v: T) -> T;

Consider removing `_false` methods

Currently, the API surface contains the following methods:

as_option_false
as_result_false
do_false
expect_false
ok_false
ok_false_with
ok_or_err_false
ok_or_err_false_with
some_false
some_with_false

All of which have a corresponding true variant. At first blush, this seems reasonable, after all, it follows the API of Option<T> somewhat, however, booleans can be trivially inverted via:

!(boolean).method()

// or

use std::ops::Not;
boolean.not().method()

I'd argue that in the general case, it'd be far more likely for someone to write some_bool.not().expect("Foo") than some_bool.expect_false("Foo"). Additionally, I'd argue that the smaller API space (10 less methods) would make the API easier to understand and use due to it's smaller size.

ps: if neither option above is okay, the first one because it requires parens around the whole expression before it, the latter because of an extra import, I'd suggest adding a fn BoolExt::invert(self) -> Self function that just does !self.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.