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

Rename Option, add Assert trait #328

Closed

Conversation

glaebhoerl
Copy link
Contributor

Another proposal inspired by an idea from @aturon. Goes well with #243, but isn't tied to it.

CLICKME

@blaenk
Copy link
Contributor

blaenk commented Sep 27, 2014

I very much agree that the name of Option can be improved. Optional is marginally better, but Maybe is the best in my opinion. While I love Haskell, I think Yes and No variants may be more practical than Just and Nothing.

Hopefully it can be considered regardless of whether or not the Assert trait proposal is accepted (I agree with that one as well), or are they intentionally tied together?

For what it's worth, Scala also uses the same name and variants as current Rust.

@bill-myers
Copy link

Yes/No is appropriate for a boolean value, not Option: there is no particular reason why "Yes" should carry a value, while there is for "Some" (if you have "some", what do you have?), and likewise for No vs None.

I think "Something(x)" and "Nothing" would be even better, but it's much longer.

@brendanzab
Copy link
Member

I'm for the status-quo. An option type indicates the presence or absence of a value - it has the 'option' of being there or not. Yes and No seem to imply that is has something to do with boolean logic, when it certainly does not.

As you can see from the linked Wikipedia page, most languages use some variant of Option, Some, and None, and if they don't it is a longer variant of those names (C++ is not listed, but it's most likely going to be std::optional in a future standard). Using Maybe, Yes, and No will just muddy the waters, and will be a source of confusion for future generations of programmers.

The way I see it, there are only two real contenders here that will make the least waves in terminology - Option, Some, and None, or Maybe, Just, and Nothing. The former reflects our ML heritage, is easier to understand, and is shorter in the None case.

@reem
Copy link

reem commented Sep 28, 2014

I am also for Option with Some and None. I could also live with Optional, but I don't think the pain of breaking all the things is worth it. -1 to Yes and No.

I still think that .assert instead of .unwrap is not a good name and know I'm not alone.

I don't see why frob(map.find(k).unwrap()) is worse than frob(assert!(map.find(k))). In fact, it looks to me like the second one actually adds an additional level of nesting parens, which can be a pain.

I like the idea of abstracting over "failing things" but this is not really unifying in the sense that I'm exceedingly unlikely to write functions which take A: Assert rather than just the underlying data. This feels like a way to bring Option and Result closer together, but falls short of what is really needed to draw them together - HKT, specifically things like Functor and Monad (call them whatever you want).

This seems to be a rather large problem to me - HKT will likely change pervasive APIs that we would like to stabilize, like Option and Result, but we won't get HKT until after we have a reasonable backwards compatibility promise. This was also a problem with AI, but those are being added pre-1.0, so it's not an issue.

// for use directly
trait Assert: AssertMsg {
fn assert(self) -> Value {
self.assert_msg("assertion failed", &(file!(), line!()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file!(), line!() calls here are pretty useless, aren't they?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. But you were never going to get an informative message here anyways (and assert_msg expects them). For comparison, if I had directly used fail!() here, it would have the same effect w.r.t. file/line numbers.

@glaebhoerl
Copy link
Contributor Author

@bjz Apparently you are not one of those, such as I, who have always been deeply dissatisfied with the existing names for these constructors (variants), in any language. Some and None are less disappointing than Just and Nothing, but it's obvious in both cases that the only reason we have these names is that we needed to have some names, and nobody had better ideas. You believe the correct goal is to "make the least waves in terminology", but Rust has been unafraid to, and indeed has sought to, choose better names than the existing ones where they were available (see also: box, Box, "lifetime", ...). But if we accept your goal as the correct one, then indeed it doesn't make sense to use different names, so we can agree to disagree on this point. (Incidentally though, our main motivation has been familiarity for C and C++ programmers, where there is no significant prior art on this count, rather than familiarity for ML, Scala, and Haskell programmers, who will likely have little difficulty picking up the language in any case.)

But also, @bill-myers, @bjz, and @reem, you are missing the whole point. The point is that Option is deeply similar to bool and to Result in both its meaning and its usage (which are two sides of the same coin) in a way that is not reflected in its current naming. We used to have an enum Either<A, B> { Left(A), Right(B) } which is conceptually "just a container" very similarly to how Option currently is, but we chose to discard that in favor of the equivalent enum Result<T, E> { Ok(T), Err(E) } which imbues these constructs with a specific meaning. Similarly, we choose to call the variants of bool true and false, which have strong connotations, rather than something unpresuming like one and other (after all, what if the programmer wanted to attach a different meaning than "true" and "false"?). There is no coherent reason for Option to be the odd type out on this score.

(This is also the answer to @blaenk's question: once you accept that these types are inextricably related, it also becomes much more obvious why extending the concept of asserting from bools to the other two also is an eminently sensible thing to do.)

I still think that .assert instead of .unwrap is not a good name and know I'm not alone.

Of course. That is why I'm trying to marshall arguments to persuade you.

(In this case, we are not so much renaming unwrap() (notice that I never mentioned it) as extending the applicability of assertions.)

I don't see why frob(map.find(k).unwrap()) is worse than frob(assert!(map.find(k))). In fact, it looks to me like the second one actually adds an additional level of nesting parens, which can be a pain.

The second one will give you a better error message, due to the magic of macros, just like assert! currently does. But you would of course be free to write x.assert() instead of assert!(x) if you preferred to.

I like the idea of abstracting over "failing things" but this is not really unifying in the sense that I'm exceedingly unlikely to write functions which take A: Assert rather than just the underlying data.

Yes, I will cop to the fact that this is overloading mainly intended for the monomorphic case. (However, it occurs to me that perhaps the methods could all be formulated in terms of the Carrier trait from #243, instead of adding new traits.)

This feels like a way to bring Option and Result closer together, but falls short of what is really needed to draw them together - HKT, specifically things like Functor and Monad (call them whatever you want).

I think these are independent. Functor and Monad are particular abstractions, Assert (or Carrier) is a different one. If it weren't for bool (but it is) the trait could be formulated using HKT rather than associated types, but it doesn't make much difference. (I agree that HKTs are great, and that we should have them as soon as possible, but they're not a magical pony which is the right solution to every problem.)

This seems to be a rather large problem to me - HKT will likely change pervasive APIs that we would like to stabilize, like Option and Result, but we won't get HKT until after we have a reasonable backwards compatibility promise. This was also a problem with AI, but those are being added pre-1.0, so it's not an issue.

Yeah. This is actually a much more pervasive problem, which arises for essentially every major language feature we might ever want to add: they all have an impact on how standard APIs would be formulated. If they didn't, they wouldn't be major. I don't see any sane solution to this dilemma other than to version the standard library as new features become available. (But it's even worse when lang items are implicated, which they often are.)

@Valloric
Copy link

The changes here are for the better. Maybe is clearer than Option. I'm not entirely sold on Yes and No (they're not bad though), although I've always disliked Some.

@kaisellgren
Copy link

I'm definitely against Yes and No. They simply don't fit. Maybe is okay and so is Optional. I like the shorter Some/None instead of the longer Something/Nothing.

@glaebhoerl
Copy link
Contributor Author

I'm definitely against Yes and No. They simply don't fit.

Then I'm hopeful that you have a compelling rebuttal to the arguments I've made?

At the risk of being blunt: the likely reason why it feels like they "don't fit" is that they have a very different connotation from the existing names, which we are accustomed to, and which therefore feel "natural". Our impulse is to perfect the existing names, and not to go in a direction that's different. But if we already had an established set of names true/false, Yes/No, Ok/Err, then I darethink that absolutely nobody would consider it a good idea to change one set of names to follow a different philosophy from the other two.

Also, Yes and No go extremely well with Maybe.

@reem
Copy link

reem commented Sep 28, 2014

I really don't see why Yes/No is a better analogue to true/false and Ok/Err than Some/None is.

@kaisellgren
Copy link

Then I'm hopeful that you have a compelling rebuttal to the arguments I've made?

Compelling? No.

But it's pretty clear that a Some/Something/Just contains some value, but a Yes containing some value? That's odd. If it's merely about whether there's a value or not, then I would be ok with it, but if the type contains a value, Yes can't be right.

@glaebhoerl
Copy link
Contributor Author

Thank you. I agree that taken in isolation, Yes does not suggest the presence of a value.

In the context of Maybe<int>, it's more apparent that Yes will mean an int and No will mean nothing. (If I say to you that you maybe get candy, then yes means you get the candy and no means you get nothing.)

I think there's also no intrinsic reason why we would assume that Ok has a value, either, other than that we are used to it.

@reem
Copy link

reem commented Sep 28, 2014

At minimum I think these should be split into two different RFCs so that we can consider the Assert proposal without heavy bike-shedding on something as huge as changing the name and variants of Option.

On the Assert proposal, on a second read-through I'm really not convinced of the utility of this thing. Your example snippet is:

let entry = map.find(key);
assert!(entry.is_some());
forbinate(entry);

and that it would be replaced with:

forbinate(assert!(map.find(key)));

However, this looks like a straw man to me - it is certainly not idiomatic rust to assert!(x.is_some()). Really, this snippet would be written like:

forbinate(map.find(key).unwrap())

which will yield an error message with exactly the same utility as the one you get using assert!. Additionally, you can write it like this:

forbinate(map.find(key).expect(format!("No key {}", key)));

which yields a much better error message, or like either of these:

match map.find(key) {
   Some(entry) => forbinate(entry),
   None => // deal with failure
}

map.find(key).map(forbinate).unwrap() // or expect, or match, or ignore it.

In short, I just see limited additional utility in the new assert as opposed to using any existing tools.

@blaenk
Copy link
Contributor

blaenk commented Sep 28, 2014

This seems to be a rather large problem to me - HKT will likely change pervasive APIs that we would like to stabilize, like Option and Result, but we won't get HKT until after we have a reasonable backwards compatibility promise.

I completely agree with this. I've noticed this kind of recognition/realization multiple times with various other topics (see collections reform RFC for example). It really makes me wish HKT were targeted for 1.0.

I do have to agree with @kaisellgren, Yes containing a value isn't very intuitive. I understand that the same can be said about Ok, but I think Yes's closer association with booleans (used as booleans in Objective-C and CoffeeScript for example) makes it more apparent. Sure, looking at its type Maybe<T> makes it clearer, but on its own, the code seems more ambiguous. The existing variants don't mislead one into thinking of booleans, such as when you're only testing if it's No.

match x {
  No => ...
  _ => ...
}

Sure, this is probably unlikely to occur given something like is_some, by the way, what would happen with utility functions like is_some? Would it become is_yes?

Another pair of variants I've come up with is Present(x) and Absent, i.e. Maybe<T> is possibly an T, in which case it'd be present, otherwise it's absent. However, they seem pretty long compared to the tradition of short variants, like Some/None, and Err/Ok. The is_present and is_absent methods would read much nicer than is_some and is_none, but otherwise I don't know if it's any better. Or Found(x)/Missing (recognizing that these types aren't only used with x.find()).

match my_map.find(name) {
    Present(entry) => display(entry),
    Absent => println!("Not found!")
}

That said, I think @glaebhoerl specifically intends to make them Yes and No or something closer to bool, based on his argument that it's related/similar to bool and Result.

I do think that Option should probably be renamed to Optional, since it conveys more meaning and is less confusing, or better yet Maybe.

@Ericson2314
Copy link
Contributor

This seems to be a rather large problem to me - HKT will likely change pervasive APIs that we would like to stabilize, like Option and Result, but we won't get HKT until after we have a reasonable backwards compatibility promise.

I also completely agree with this.


I prefer Option and Either, because both are more "use-case agnostic", i.e. those names get at what the types really represent rather than how one might use them. I hadn't thought of Either's name change until @glaebhoerl mentioned it, however, and I admit it motivates switching to "Maybe" to be consistently promote the error handling / control-flow use-case.


I need to look at the details such as two-traits and debug_assert more, but I like the idea of having assert! a) return types besides unit, and b) unwrap these types is a good one. This, along with fn trace<A>(A, &str) -> A, makes "functional debugging" easier at no cost.

@thestinger
Copy link

The option module and Option type are marked #[stable]. If the stability markers aren't just fluff that means it can't be renamed. I don't agree with the closed process being used for stabilization, but I think the past choices need to be respected as long as the process is still in place.

@pczarn
Copy link

pczarn commented Sep 29, 2014

A simple lookup with find can be interpreted as follows:

What's the value for the key "foo" in the map?
– There is some "bar".
– None.

But with renamed variants, it reads differently:

Does the map contain a value for the key "foo"? If so, what is it?
– Yes. It is "bar".
– No.

This renaming wouldn't be a good fit for a declarative style where values usually aren't answers to questions.

@aturon
Copy link
Member

aturon commented Sep 29, 2014

This seems to be a rather large problem to me - HKT will likely change pervasive APIs that we would like to stabilize, like Option and Result, but we won't get HKT until after we have a reasonable backwards compatibility promise.

I completely agree with this. I've noticed this kind of recognition/realization multiple times with various other topics (see collections reform RFC for example). It really makes me wish HKT were targeted for 1.0.

I want to clarify this point in particular, because I think it's pretty important.

Over the past couple of months, we've been carefully considering various API stabilization needs and the way they interact with language features -- and that was one of the main reasons that we decided to pursue multidispatch and associated items prior to 1.0.

However, HKT is going to be a significant undertaking, and we can largely work around its absence for now by focusing primarily on "concrete" APIs, rather than highly-generic (abstract) programming through traits. We've also been thinking hard about the migration path from concrete to abstract, to make sure that we'll be able to add traits using HKT later on, without breaking compatibility or leaving around a lot of junk. That's the direction taken by the collections reform RFC, it's what we're starting to look at for std::num, and I think will serve us well in general for 1.0 APIs.

Put differently, I don't think that HKT will change concrete APIs, but will rather provide a means of generically programming over them. The best thing we can do now to anticipate that future is to ensure as much consistency across APIs as we can, and to avoid settling on traits that are best expressed using HKT unless there is a clear migration path.

(There are lots of interesting details regarding these plans, but that's veering off topic. Feel free to ping me on IRC if you want the gory details.)

@aturon aturon self-assigned this Sep 29, 2014
@aturon
Copy link
Member

aturon commented Sep 29, 2014

@glaebhoerl Thanks for yet another thorough and well-written RFC!

I need to digest the ideas here, but a couple of immediate thoughts:

  • I agree that the two parts of this RFC -- renaming Option and the Assert trait -- can and should be treated independently. I understand why you see them as coupled together, since you see both of them as bringing bools, Option and Result closer together, but I think each proposal can also stand alone.
  • For Option vs Optional, I think in general we've tried to stay away from extra grammatical baggage when we can (e.g. Clone vs Cloneable). This also fits with our tradition of short keywords. I don't think it's a huge deal, but Option subjectively feels more Rustic to me than Optional.
  • FWIW, I do think that Maybe/Yes/No go very well together.
  • I understand your thinking on the naming of Option, but I also think the ideal naming depends on how you're framing its use. Some and None to me more immediately denote the presence or absence of a value than Yes or No -- for the latter, I need to be thinking about a question, but the question is often "is there a value here?" Which choice feels better for a particular bit of code will probably depend on the specific API.
  • Part of the motivation for moving away from unwrap is just that the same name is used for quite different operations. But that's not the case for Option/Some/None. I think there's a reasonable way to understand these names as they are; they're also memorable and distinct.
  • In general, I wonder how people would feel about something like this assert idea if the name was different -- expect or ensure, for example? How much of the worry is that the word "assert" has deep meaning in existing languages?
  • As @thestinger points out, the Option type has already been marked #[stable]. In general with stable APIs, it's still possible to rename via deprecations, and the deprecated versions can be dropped at the next major release (in the semver sense). Since we're not yet at 1.0.0, essentially each release is considered a major one and breakage is still permitted (though we should avoid that as much as possible with #[stable] APIs). See the semver rules for more detail (although the exact mechanics for us haven't been fully nailed down.)

I need to think more about the proposed traits and macro unification. More feedback later!

@nikomatsakis
Copy link
Contributor

My two cents:

I am opposed to changing the name of Option -- it seems like a lot of churn with little gain, and the name Option has a long and storied history to it. The idea of an assert trait I find appealing at first glance. I have to re-read the document again to let it sink in a bit more.

@glaebhoerl
Copy link
Contributor Author

@aturon Thanks for the perceptive comments!

For Option vs Optional, I think in general we've tried to stay away from extra grammatical baggage when we can (e.g. Clone vs Cloneable). This also fits with our tradition of short keywords. I don't think it's a huge deal, but Option subjectively feels more Rustic to me than Optional.

I completely agree with this reasoning in the abstract. I like our short names. I think Optional would be longer than is ideal. (Hence Maybe.)

What seriously bothers me, however, is when name-shortening has the effect of changing the meaning, or at least, of suggesting a different meaning from the one which was intended. Arc is the most prominent example of this; that should be something like AtomicRc. Pod was another, thankfully we've already dispensed with it. And Option, as well, simply does not have the same meaning as Optional in the same way that Clone and Cloneable do. What do I think of upon hearing the word "option"? Stock option. Configuration option. Nothing like what we're aiming for. The fact that this is such an important part of the language - our alternative to null pointers - makes a good intuition here even more important, in my mind.

I understand your thinking on the naming of Option, but I also think the ideal naming depends on how you're framing its use. [...]

I agree with this also. I think there is merit to both the "data-oriented" nomenclature that Haskell tends to prefer, and which our Option type has, and also to the more opinionated, for lack of a better word "meaning-oriented" naming that we have for Result, and which I propose for Maybe. On balance I've come to prefer the latter. (Individual programs can, and should, define their own types if they want a different connotation - so the standard types being "vanilla" enough to fit every situation is not so critical.) But apart from that preference, we are also currently simply inconsistent about this.

For the record - while the Option name has bothered me ever since I first discovered Rust (two years ago?), changing it has been one of my lowest priorities; and Some/None, while not necessarily ideal, are also completely adequate, and wouldn't on their own merit an effort to come up with replacements. It's only after I hit upon the Maybe/Yes/No names and the connection to assertions that I thought that all of this together was exciting enough to put forward. (My hope was that these connections and the more general formulation would lead people to see the light on assert...)

I need to think more about the proposed traits and macro unification.

I still have the feeling that there should be a better way to do this - this is just the best I've managed to come up with. It could be formulated in terms of Carrier from #243 somehow like this:

fn assert_msg<T, Car: Carrier<Normal=T>>(self: Car, msg: &'static str, file_line: &(&'static str, uint)) -> T { ... }

fn assert_fmt<T, Car: Carrier<Normal=T>>(self: Car, fmt: &fmt::Arguments, file_line: &(&'static str, uint)) -> T { ... }

fn assert<T, E: Any + Send, Car: Carrier<Normal=T, Exception=E>>(self: Car) -> T { ... }

(I'm not going to go through the motions of the so-called "extension trait" dance here.)

In the case of assert, the Exception type of bool and Maybe is (), which satisfies Any + Send trivially, so this works out fine as far as that goes. What is lost relative to the formulation in the text is that () would be the value passed to task failure, rather than a string such as "assertion failed". Perhaps not a huge deal, especially if assert!() would be used with these types most of the time (which goes through assert_{msg,fmt}).

@ghost
Copy link

ghost commented Sep 30, 2014

+1 for renaming Option to Maybe.

To me, Maybe<T> clearly says what it is: "Maybe a T", or in other words: "Either some T or not a T". Whereas Option<T> sounds like "Option to be a T" which isn't exactly what it is.

@Ericson2314
Copy link
Contributor

Option vs Optional has always been the same to me. Colloquially List<A> is "List of As", and Option<A> is "Option of a", like "you have an option of this or that".

@alexchandel
Copy link

The wikipedia article is called Option type, for whatever that's worth.

If we rename Option to Maybe to parallel Haskell, let's not create a new convention. Haskell defines their Maybe with Nothing and Just,

data Maybe a
    = Nothing
    | Just a

So Rust definition would be:

enum Maybe<A> {
    Just(A),
    Nothing
}

@cgaebel
Copy link

cgaebel commented Nov 6, 2014

I disagree with changing from Some and None to Yes and No, as well as Nothing and Just.

Why?

Because both Some and None are 4 characters long, and usually laid out vertically. The symmetry is pretty, and that makes the names pretty. I would be open to other possible namings as long as they kept the symmetry, but at this point it's not worth the code churn as Alex said.

Consider:

match my_option {
  None         => println!("No number!"),
  Some(number) => println!("My number is: {}", number),
}

compared to:

match my_option {
  Nothing      => println!("No number!"),
  Just(number) => println!("My number is: {}", number),
}

@arcto
Copy link

arcto commented Nov 6, 2014

I like Maybe<Value> with yes and no, and think it's intuitive and clear that a yes in this case would entail a Value.

They are shorter and more obvious than Option, really. I'd be very much in favour of this change if in a separate RFC.


As for error messages, I might have missed this in the document, but to many of us it is very important not to include debug information such as file!(), line!(), and stringify!($cond) in release/production code. Various error handling propositions lately have been very heavy on including error context and descriptions and such. In embedded systems these conveniences carry too much of a cost if they cannot be disabled.

@glaebhoerl
Copy link
Contributor Author

(I'm quite willing to split up the RFC if the core team wants to accept any of the parts separately. But there's no point in doing the work if they're going to be rejected either way.)

@vadimcn
Copy link
Contributor

vadimcn commented Nov 9, 2014

+1 for enum Maybe<T> { Some(T), None } - it reads somewhat better than Option<T>, but yeah, this is quite late in the game.
+1 for assert!(...). Might also consider calling it check!(...).

@mitchmindtree
Copy link

+1 for Maybe<T>, however I do feel that Some(T) and None are a little more intuitive than Yes and No in relation to their actual uses

@alexchandel
Copy link

👎 for the names in this RFC.

OCaml, Scala, Standard ML, Swift, and Coq all use Option/Some/None. The wikipedia article for the option type is even called "Option type". C++ and Java also use Optional but don't have names for the variants. So there is strong precedent for the name Option, and the variants Some and None.

Alternatively, the functional languages Haskell, Idris, and Agda all use Maybe/Just/Nothing.

In either case, None and Nothing are sensible constructors for the unit type. If we wanted to be different, Empty or Unit also fit conceptually. But No doesn't. Similarly, Just(T) and Some(T) make sense as constructors. If we wanted to be different, A(T) or Any(T) might make sense. But Yes(T) doesn't.

@aturon
Copy link
Member

aturon commented Feb 3, 2015

@glaebhoerl I sincerely apologize for the long-overdue attention on this RFC! I'm trying to go through my backlog and found this one.

As I think is probably obvious, there's not a strong consensus for the naming change here, which I think would be needed to make such a change at this point.

Regarding the changes to the assert macro, it seems best to begin with an implementation on crates.io that people can begin using today (albeit with a different name than assert for now). If this is something that people find useful in their projects and would like to see made part of std, we can add it backwards-compatibly later on.

I'm going to close the RFC for the time being. Thanks @glaebhoerl!

@aturon aturon closed this Feb 3, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.