Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The parameter ordering of `Option::map_or_else` is unintuitive. #1025

Closed
abonander opened this Issue Mar 31, 2015 · 19 comments

Comments

Projects
None yet
@abonander
Copy link

abonander commented Mar 31, 2015

The signature of Option<T>::map_or_else is as follows:

fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, def: D, f: F) -> U

This is highly unintuitive as the two closure parameters are in the reverse order of that suggested by the function's name. This means that anyone who tries to invoke it from memory, including myself earlier today, is going to run into a compiler error on the first try.

Example:

let first_word_letter_count = "hello world".words().next().map_or_else(
    |word| word.len(),
    || 0
);

The above makes sense, logically. If the value is there, map it and return the result. Otherwise (implying a secondary operation), call a closure which will produce a substitute value. Even the documentation supports this logical progression:

Applies a function to the contained value or computes a default.

However, trying to invoke the method this way will result in a compiler error because the no-arg closure is required to be first. The particular reasoning behind this design is not given, but I have found precedent in Haskell's maybe function:

maybe :: b -> (a -> b) -> Maybe a -> b

However, I don't think Haskell's intuition for parameter ordering can extend to Rust because Rust doesn't have currying, partial application, or (global) lazy evaluation. And even in Haskell the ordering isn't necessarily intuitive.

I am aware that this method and Option itself have both been marked as Stable. However, I believe minor unintuities like this add up and ultimately reflect poorly on the user experience of the language as a whole, and should be addressed before Rust hits 1.0.

I would like to note that I am willing to apply the effort to adjust this myself, as it's relatively trivial. However, because it's trivial, I am not sure if it requires an RFC or just a general community agreement.

@reem

This comment has been minimized.

Copy link

reem commented Mar 31, 2015

I agree that this ordering is unintuitive and it would be better if the Some case was first.

@frewsxcv

This comment has been minimized.

Copy link
Member

frewsxcv commented Mar 31, 2015

Yes. I get this wrong every single time. 👍

@cmr

This comment has been minimized.

Copy link
Member

cmr commented Mar 31, 2015

To be honest I thought the order was always the other way around. Was it changed?

@abonander

This comment has been minimized.

Copy link
Author

abonander commented Mar 31, 2015

@cmr I thought it was always the other way around too. That's what screwed me up and motivated me to open this issue.

@huonw

This comment has been minimized.

Copy link
Member

huonw commented Mar 31, 2015

The order is currently chosen to be internally consistent with map_or. map_or takes the "main" closure last because that is syntactically much nicer:

foo.map_or(some_value, |x| {
    bar();
    baz(x);
    qux();
})

vs.

foo.map_or(|x| {
    bar();
    baz(x);
    qux();
}, some_value)

This isn't to say that we shouldn't change it, but we shouldn't consider this in isolation.

@abonander

This comment has been minimized.

Copy link
Author

abonander commented Mar 31, 2015

@huonw That's understandable but still rather unintuitive. I would prefer to make it look nicer with a minor style tweak:

foo.map_or(|x| {
        bar();
        baz(x);
        qux();
    },
    some_value
)

or even:

foo.map_or(
    |x| {
        bar();
        baz(x);
        qux();
    },
    some_value
)

This does get a little more right-drift but I think it's worth the concession for cleaner, more intuitive code. I use the docs all the time but if I have to consult them to figure out the right argument ordering for something as simple as an operation on a monad, it's going to severely impact my efficiency.

@lilyball

This comment has been minimized.

Copy link
Contributor

lilyball commented Apr 4, 2015

I don't think I've ever screwed this one up. It just seems natural to me that the default value comes first. Probably because map_or() is "more fundamental" than map_or_else, so to speak (it has the shorter name and comes first in the documentation, and I would not be surprised to learn that it is the more popular of the two methods in actual usage).

@crazymykl

This comment has been minimized.

Copy link

crazymykl commented May 31, 2015

@kballard, but even map_or has odd ordering. I'd expect the arguments to be, in order: map through this, or give me that.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented May 31, 2015

So obviously this can't be "fixed" in 1.x, and fixing it in 2.x would be a disaster. So... this bug isn't actionable?

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented May 31, 2015

@Gankro I believe deprecating this for an alternative with a name that does not cause wrong associations with parameter ordering might make it actionable. But in general, ditto.

@tshepang

This comment has been minimized.

Copy link
Contributor

tshepang commented May 31, 2015

@Gankro why would it be a disaster? People will get a compile error, and the fix is simple.

@frewsxcv

This comment has been minimized.

Copy link
Member

frewsxcv commented May 31, 2015

People will get a compile error, and the fix is simple.

This might be true, but it would be pretty devastating to have virtually all Rust projects break just because some people aren't satisfied with the naming of argument order a method

@tshepang

This comment has been minimized.

Copy link
Contributor

tshepang commented May 31, 2015

@frewsxcv it's the re-ordering of arguments, not the renaming. Besides, people are free to stay with an ancient tool if doing a simple fix is too much effort. Some of these fixes could be automated even, especially simple ones like this.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented May 31, 2015

@tshepang This is not a good way for a language developer to act. Breaking client code in such a blatant way should not be taken so lightly.

@tshepang

This comment has been minimized.

Copy link
Contributor

tshepang commented May 31, 2015

@Gankro I think "fixing mistakes and oversights" is supposed to be part of 2.x. Some (more important) stuff will be broken anyways, so we might as well break the smaller things as well. I think making things more intuitive is worth the pain.

@frewsxcv

This comment has been minimized.

Copy link
Member

frewsxcv commented May 31, 2015

fixing mistakes and oversights

As stated above in previous comments, the argument order is intentional; it is neither a mistake nor an oversight.

@tshepang

This comment has been minimized.

Copy link
Contributor

tshepang commented May 31, 2015

I hear you, but a mistake can happen whether or not there is intent. And as this issue has proven, people do not like this choice.

@Stebalien

This comment has been minimized.

Copy link
Contributor

Stebalien commented Aug 20, 2016

Triage: ⛵️.

@steveklabnik

This comment has been minimized.

Copy link
Member

steveklabnik commented Aug 20, 2016

Yup, this ship has sailed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.