Bool::into_option(closure) yields None or Some(closure())


#1

I just had a situation similar to the first example in the std::option module docs:

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

I had a condition, and depending on the condition I either created a None or a Some(value). So I thought, wouldn’t it be nice if I could just call a function on the condition that returns a Some(value) if the condition is met, and a None otherwise. So I wrote up some magical function, to see if it would look weird.

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    (denominator != 0.0).into_option(|| numerator / denominator)
}

I’d like to get some opinions on the readability. Will this create more confusion than it’s worth? Also, it’s not really enough for its own crate, so if there are other suggestions that go along the same lines, a more general crate collecting similar traits and functions might be the right way to go.

##Detailed Implementation:

Fully runnable example in the PlayPen.

trait BoolExt {
    fn into_option<T, F>(self, f: F) -> Option<T> where F: Fn() -> T;
}

impl BoolExt for bool {
    fn into_option<T, F>(self, f: F) -> Option<T> where F: Fn() -> T {
        if self {
            Some(f())
        } else {
            None
        }
    }
}

#2

If we put the inverse of this in the standard library, we could implement BitOr<Fn()> and create an OrElse operator a la C++'s goes to operator (as in while (x --> 0) {}):

impl<T, F: Fn() -> T> BitOr<F> for bool {
    Output = Option<T>;
    fn bitor(self, f: F) -> Option<T> {
        if self {
            Some(f())
        } else {
            None
        }
    }
}
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    denominator == 0.0 ||| numerator / denominator
}

/joke

Back to the topic, personally, I prefer Iff: http://is.gd/wWVWdD

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    (|| numerator / denominator).iff(denominator == 0.0)
}

#3

your link goes to my playpen.

also:

denominator == 0.0 |(|| numerator / denominator)

:smiley:


#4

Fixed. That ambiguity is unfortunate (along with the fact that impl would conflict with rust’s BitOr<bool> for bool impl. However, the following iff then operator would work (playpen http://is.gd/YD74XN):

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    iff (denominator != 0.0) >>|| numerator / denominator
}

fn main() {
    println!("{:?}", divide(55.0, 0.0));
    println!("{:?}", divide(55.0, 5.0));
}

#5

I like your proposal, though I think OP’s proposal feels more rusty.


#6

Actually, I’ve found the following to be fairly useful:

trait Iff: Sized {
    fn iff<F: FnOnce(&Self) -> bool>(self, f: F) -> Option<Self> {
        if (f)(&self) { Some(self) } else { None }
    }
}

impl<T> Iff for T {}

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    denominator.iff(|&d| d != 0).map(|d| numerator / d)
}

#7

I don’t see how that is in any way an improvement over

if denominator != 0 { Some(numerator / denominator) } else { None }

It seems far more verbose, repetitive, and complex.


#8

In this case, you’re probably right. It helps when you really don’t feel like writing a semicolon but an actual if statement is probably clearer.

Example:

fn get_name() -> Option<String> {
    read_stdin().iff(|s| !s.is_empty())
}

#9

This is essentially short-circuit ‘and’ evaluation. If rust allowed overloading of the logical operators, you could do this:

(denominator != 0) && Some(numerator / denominator)

Much nicer IMO, and it doesn’t require a closure


#10

Sorry, but I have to disagree. The && operator is designed to do a short-circuit boolean AND. This works well because the types on both sides are booleans and the result also is. Now if you put Option into the mix, you’ll have to answer what happens when someone puts an option to the left side of the &&? What if we have an option on both sides?

let o1 = Some(..);
let o2 = None;
let b1 = true;
let b2 = false;

b1 && b2; // evaluates b1, then b2, returns false: bool
b1 && o1; // evaluates b1, then o1, returns Some(..): Option
b2 && o1; // evaluates b2, returns None: Option
o1 && b2; // ?
o2 && b1; // ?
o1 && o2; // ???
(b1 && o2) && (o1 && b2); // !?
o1 && b1 && o2 && b2 && Some("This is no longer funny"); // don't you agree?

Either we’d make the right-hand side type the result type (which would at least be a clear-cut rule), which could still confuse the hell out of people, or we’d make having a left-hand-side Option illegal (by not implementing the logical operator for this case), which would probably still be a footgun.


#11

Sorry, but I have to disagree. The && operator is designed to do a short-circuit boolean AND. This works well because the types on both sides are booleans and the result also is. Now if you put Option into the mix, you’ll have to answer what happens when someone puts an option to the left side of the &&? What if we have an option on both sides?

Plenty of languages support short-circuit AND on non-booleans, eg. javascript - there’s nothing particularly problematic about it. The simplest rule would be to take the type of the right-hand side as you say, and all that’s required is that the RHS type has a “falsey” value, but you could equally well go the whole way and accept arbitrary types on both sides:

(a: A) && (b: B) where &A: Into<bool>, A: And<B>

=>

if a.into() { a.and(b) } else { a.falsey() }

Where And<B> has an associated type which determines the return type of both “a.and(b)” and “a.falsey()”


#12

<meta content-type="melodramatic-ranting/deliberately-overwrought;degree=slight">

The degree to which you’re willing to sabotage readability in pursuit of a trifling improvement in writing ergonomics is terrifying nay, horrifying.

Nothing but bool should have any sort of “truthiness”. The alternative leads to painful situations like Python’s old “midnight is False”, because everyone and their dog feels compelled to come up with some kind of “falsy” value for every type.

Even the smallest scrap of readability should be conceded only for significant gains. Losing “only bool is logical” to avoid some method calls is nowhere near enough.

Please, no. Just no.


Off-topic aside

eg. javascript - there’s nothing particularly problematic about it

I cannot find words to express how much I don’t agree with you.


#13

Thanks, DanielKeep. You wrote much more eloquently what I wanted to say. I only wanted to add that this leads to people being ‘clever’ instead of doing the clever thing and writing readable code.

I’m sure it wouldn’t have taken long for someone clever to figure out that ! matches all types, so he could write Perl: mybool || panic!(...).


#14

An alternative could be adding a to_option(self) -> Option<()> method and using existing map or and_then from Option.

trait BoolExt {
    fn to_option(self) -> Option<()>;
}

impl BoolExt for bool {
    fn to_option(self) -> Option<()> {
        if self {
            Some(())
        } else {
            None
        }
    }
}

The modified example: http://is.gd/OFszo8


#15

I like it. But I’d favor a shorter name for to_option. Maybe to_opt? Or iff for that matter, as it was purposed here earlier.


#16

I’d be tempted to just copy+paste the methods from Option, and drop the closure argument, treating bool as though it were Option<()>. That way, there aren’t any new method names to learn.


#17
type bool = Option<()>;
const false: bool = Some(());
const true: bool = None;

like Ada’s bool is a library-type called Boolean:

type Boolean is (False, True);

#18

[quote=“kstep, post:15, topic:1729, full:true”] I like it. But I’d favor a shorter name for to_option. Maybe to_opt? Or iff for that matter, as it was purposed here earlier. [/quote]The abbreviation would be frowned upon. And this function doesn’t feel like if. Maybe it could just be option.


#19

Something along these lines?

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    (denominator != 0.0).map(|| numerator / denominator)
}

Looks interesting.


#20

I would be confusing by the name “map” here, as map always maps a function over one or more elements. Here, the function gets no arguments. to_option or and_then would be much better.