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

RFC: Imply Option #2180

Closed
wants to merge 10 commits into from
Closed

RFC: Imply Option #2180

wants to merge 10 commits into from

Conversation

Dynisious
Copy link

@Dynisious Dynisious commented Oct 18, 2017

Specification

My RFC for adding some function implementation for the Option type akin to the implies or -> operation from predicate logic. Useful for getting rid of boiler plate code and making the programmers intention clear in their code.

Updated Introduction
The conversation has been going on and things have changed in the specification; I have created a more appropriate introduction further down but left this one unchanged to keep the context of the earlier conversation.

My RFC for adding some function implimentation for the Option type akin to the `implies` operation from predicate logic.
@Dynisious Dynisious changed the title Imply Option RFC: Imply Option Oct 18, 2017
Formatted poorly displayed code and pseudo-code to display correctly and be legible.
@SimonSapin
Copy link
Contributor

I am not familiar with predicate logic. Can this RFC be described as “add a pair of constructors to Option that are based on a boolean”? This does sound useful, I’ve sometimes written code that this would have made simpler.

@Dynisious
Copy link
Author

Yes @SimonSapin that is the idea in layman's terms. "If some boolean is true I want this value (Some(x)) but if this boolean is false throw it away (None)."

@petrochenkov
Copy link
Contributor

This would be a useful addition, but I'd personally want to see it as a method on bool.

// These methods are just copied from `Option`
impl bool {
    fn xxx<T>(self, b: T) -> Option<T>; // This one is missing on `Option`
    fn map<T, F>(self, f: F) -> Option<T>  where F: FnOnce() -> T;
    fn and<T>(self, optb: Option<T>) -> Option<T>;
    fn and_then<T, F>(self, f: F) -> Option<T>  where F: FnOnce() -> Option<T>;
}

let my_opt_value = my_bool.xxx(get_value());
let my_opt_value = my_bool.map(|| get_value());
let my_opt_value = my_bool.and(get_opt_value());
let my_opt_value = my_bool.and_then(|| get_opt_value());

@oli-obk
Copy link
Contributor

oli-obk commented Oct 18, 2017

Previous discussion on internals: https://internals.rust-lang.org/t/bool-into-option-closure-yields-none-or-some-closure/1729

The general consensus seemed to be to use the boolinator crate

@Centril
Copy link
Contributor

Centril commented Oct 18, 2017

@petrochenkov While I agree they should be methods on bool... The name and_then feels like a monadic >>= (bind), which isn't optimal, since self : bool... tho I get viewing bool as Option<()> which is why reusing the method names on Option is compelling. I'm torn... :(

@eddyb
Copy link
Member

eddyb commented Oct 18, 2017

type bool = Option<()>; would've been an interesting decision if done early on.
That or involving homotopy (with Result<(), ()> too) but that's an active area of research.

Copy link
Contributor

@Centril Centril left a comment

Choose a reason for hiding this comment

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

Typos & such improvements (mostly)

# Motivation
[motivation]: #motivation

This addition will increase the legability of code segments and assist in defining the thought processes and motivations of programmers through code. The use cases of this addition are solutions which are expressable in the following predicate form:
Copy link
Contributor

Choose a reason for hiding this comment

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

legability -> legibility.

None
}
```
The outcome of this addition will reduce repeated code which introduces bugs during refactoring and present the thought process of the programmer in a clearer fasion through their code.
Copy link
Contributor

Choose a reason for hiding this comment

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

fasion -> fashion


The `Option` type is extremely useful when your code may or may not yield a return value.
Such code may looks similar to this:
```
Copy link
Contributor

Choose a reason for hiding this comment

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

extremely useful is probably true, but also too much. useful is probably sufficient.

```
But the `else` branch is required for returning `None` value if `x == 0` evaluates to false.
Fortunately Rusts `Option` type has functionality get rid of the unecessary code:
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing a to between functionality and get.


Option::on_pred(x == 0, x)
```
This code performs the exact same function as our original `if` statement however our code is compressed to a single line and our intentions are just as clear.
Copy link
Contributor

Choose a reason for hiding this comment

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

Change to: "This code has the exact same behaviour as our original if statement. Our code is however compressed to a single line and our intentions are just as clear."

}
}
```
This implementation covers the use cases I've proposed in my earlier examples and any others of similar form without any external dependancies; this should make the implementation stable as Rust continues to develope.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • dependancies -> dependencies
  • depersonalize
  • develope -> develop

# Drawbacks
[drawbacks]: #drawbacks

This is a functionality which has functional programming and monads in mind with its design and this may make it another stepping stone to be learned for programmers which are new to Rust or functional programming concepts.
Copy link
Contributor

Choose a reason for hiding this comment

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

Rust already has a lot of FP concepts and actual monadic bind in Option::and_then, Iterator::flat_map in libcore - I don't see why adding a small addition like this would be anything new FP-wise.

Copy link
Author

Choose a reason for hiding this comment

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

I'm not saying it would be anything new, rather it would be just one more little thing which needs to be learned when considering new programmers.

# Rationale and alternatives
[alternatives]: #alternatives

The implementation I've proposed is clear and easily documented and is the minimal ammount of code and change necessary to add this into the Rust language without sacrificing any of the advantages of the `if/else` blocks.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Depersonalize

Option::lazy_pred(true, || x)
```
It is very little boiler plate code compared to the `if/else` alternative but it is suboptimal from an execution standpoint and a more obtuse implementation for new Rust programmers to learn.
- Not including the `lazy_pred` function. However, as discussed, this leaves the `on_pred` function at a disadvantage when the equivilant `if` block is computationally intesive as it wastes computation on a value which may simply be discarded.
Copy link
Contributor

Choose a reason for hiding this comment

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

equivilant -> equivalent

```
It is very little boiler plate code compared to the `if/else` alternative but it is suboptimal from an execution standpoint and a more obtuse implementation for new Rust programmers to learn.
- Not including the `lazy_pred` function. However, as discussed, this leaves the `on_pred` function at a disadvantage when the equivilant `if` block is computationally intesive as it wastes computation on a value which may simply be discarded.
- Providing syntax support for this implementation in Rust (similar to the `?` operator for the `Result` type). However I argue that pushing the abstraction of the logic this far reduces the clarity of the code and the expression of the programmers intention. Additionally discussion has yet to addiquately cover syntax support for both the `on_pred` and `lazy_pred` functions in a meaningful manner and removing either one is disadvantages as discussed above.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • addiquately -> adequately
  • disadvantages -> disadvantageous

@Centril
Copy link
Contributor

Centril commented Oct 18, 2017

@eddyb and const true: Option<()> = Some(()); const false: Option<()> = None;. Yeah, let's do HoTT in 15 years ;)

@est31
Copy link
Member

est31 commented Oct 18, 2017

"I’ve sometimes written code that this would have made simpler." is IMO not a good argument to add a completely new operator to the language. It needs to be used really really often for that to be the case. If there is one special area where the code would benefit from it, just define a macro.

@Centril
Copy link
Contributor

Centril commented Oct 18, 2017

@est31 Was an actual operator proposed (with sigils and stuff...) or just a method? The bar for the former is of course significantly higher. But the bar is still high for methods.

@SimonSapin
Copy link
Contributor

It’s a methods. (Or rather, two.)

@withoutboats withoutboats added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Oct 18, 2017
Fixed some spelling errors spotted by @Centril
@Dynisious
Copy link
Author

Dynisious commented Oct 18, 2017

I do feel that @est31 has a point in saying that a macro could be the appropriate measure to take (I do often forget about macros). An implementation such as the following could possibly be better:

macro_rules! on_pred {
    ($pred:expr, $result:expr) => (
        if $pred {
            Some($result)
        } else {
            None
        }
    )
}

This has the additional benefits of being inherently lazy, eliminating the need for a separate lazy_pred, if I understand how macros evaluate expressions correctly, else a lazy_pred macro would be trivial.
However, I do feel that abstracting this away into a macro loses the explicitness of a constructor:

Option::on_pred(x == 0, x) //It is very clear here that I am constructing an `Option` type.
on_pred!(x == 0, x) //May be functionally identical but is less explicit and informative about what exactly is being done.

@est31
Copy link
Member

est31 commented Oct 18, 2017

@SimonSapin @Centril @Dynisious oh sorry, I've only skimmed the proposal and got confused a little. Disregard my comment then! Adding a method is entirely reasonable. 👍

@eddyb
Copy link
Member

eddyb commented Oct 18, 2017

@Centril Yeah, I was implying those definitions for true and false. We could do that now (with rust-lang/rust#45225 Option<()> is literally represented the same way bool is) but it would break any code that implemented a trait for both Option and bool.

@Centril
Copy link
Contributor

Centril commented Oct 18, 2017

@eddyb That sounds like a non-starter ;)

@scottmcm
Copy link
Member

Could the syntax for on_pred!(p, r) literally just be if p { r }? (Or maybe if p { Some(r) }.)

Strawman proposal for the latter:

  • Change the desugar from if A { B } => if A { B } else { () }
    to if A { B } => if A { B } else { Default::default() }.
  • Existing code has typeof(B) == (), and unit has a unit default, so existing things (mostly) work.
  • For new things like let z = if x != 0 { Some(x) };, the type comes from the block.

Improvement opportunities for the strawman:

  • This would break inference in some cases, like if p { Default::default() }. I think that's an allowed break, but practicality would of course depend on the actual impact of such a thing.
  • Using straight Default might not be the best choice, since as described it means if x != 0 { x } is equivalent to just x. A different (possibly forever-unstable) trait that produces either () or Try::from_error(Default::default()) might be a more reasonable restriction.
  • Making it be if p { r } would require something like ok-wrapping, for better or worse.

Non-serious proposal for the bool <=> Option<()> discussion:

struct False;
struct True;
impl ops::Try for bool {
    type Ok = True;
    type Error = False;
    fn into_result(self) -> Result<True, False> {
        if self {
            Ok(True)
        } else {
            Err(False)
        }
    }
    fn from_ok(_: True) -> Self { true }
    fn from_error(_: False) -> Self { false }
}

@Dynisious
Copy link
Author

@oli-obk I have looked into the boolinator crate after you've mentioned it and I do recognise the similarities. However, the discussion here is about implementing similar functionality in the standard rust libraries; if the consensus of the community is that the full implementation of boolinator should be included in the standard libraries then that can go ahead but I doubt that will be the case. boolinator adds several functions, such as as_option or ok_or, which do not have wide enough applications to be included as standard in my opinion.

@Dynisious
Copy link
Author

@petrochenkov Implementing this functionality as methods on bool rather than as constructors for Option is an interesting suggestion though I don't intuitively see the advantage gained by this change.
Additionally the and and and_then methods you've suggested don't seem to lend anything extra to the language verses:

let return_value = my_bool.xxx(my_value).and(option_value)
let return_value = my_bool.xxx(my_value).and_then(|| option_value)
let return_value = my_bool.map(|| my_value).and(option_value)
let return_value = my_bool.map(|| my_value).and_then(|| option_value)

@leoyvens
Copy link

leoyvens commented Oct 19, 2017

It seems better to just add From<bool> for Option(()) and piggyback on the Option methods, example:

my_bool.into().map(|_| my_value)

I don't think you need an RFC to add that impl just a PR with a final comment period.

@Centril
Copy link
Contributor

Centril commented Oct 19, 2017

@leodasvacas I think that From<bool> for Option(()) should be added irrespective of merging this RFC or not. However, .into().map(|_| myvalue) seems more wordy compared to a more direct approach.

@petrochenkov
Copy link
Contributor

my_bool.into().map(|| my_value)

I'm pretty sure this won't pass type inference.

@leoyvens
Copy link

leoyvens commented Oct 20, 2017

My suggestion was mistaken, we don't need a new impl we already have such an impl would conflict with From<T> for Option<T>, and inference can't figure it out anyways as @petrochenkov said.

I can restate my sugestion as adding a method to bool that would make the conversion, such as fn optionalize(self) -> Option(()). Even that long name is the same length as the RFC proposal.

my_bool.optionalize().map(|_| value)
Option::lazy_pred(my_bool, |_| value)

@Dynisious
Copy link
Author

I agree that it could be advantageous to convert bool to Option<()> rather than constructing an Option<T> on a bool; from my understanding it would evaluate in a functionally identical manner to my original constructor suggestions with the advantage of applying currying to the implementation.

@Centril
Copy link
Contributor

Centril commented Oct 25, 2017

@leodasvacas .optionalize() is a bit long... how about .iso() for isomorphism?

@ssokolow
Copy link

@Centril .iso() is not intuitive enough and I'm willing to bet a lot of programmers who did discover it would wind up having to re-consult the manual a few times before it sticks.

...both because "isomorphism" is a very jargon-y word that I'd actually never heard before (and I've been programming in languages no more functional than Python for over 15 years) and because abbreviating it to "iso" is like having a crate named "api-binding". There are simply too many things "iso" could be short for.

@scottmcm
Copy link
Member

What do you think about generalizing for Try?:

The usual problem with generalizing the return type to a generic is that it needs to be inferred from context. my_bool.bikeshed_me(|| foo()).map(bar) wouldn't compile.

For this case specifically, the strawman posted wouldn't even work with Option, since there's no () -> NoneError conversion through Into. I suppose it could be generalized to E: Default or something, but really I prefer just supporting Option, as a perfectly reasonable representation of "you only provided my the success value". There's always ok_or and friends if they really wanted a Result (and didn't just use an if), and the usual interconversion rules of ? are there if that was desired.

@Centril
Copy link
Contributor

Centril commented Dec 10, 2017

@scottmcm Fair enough =)

@aturon aturon self-assigned this Jan 23, 2018
@JustAPerson
Copy link

I think adding the methods on the bool impl would be great. Definitely have wanted this in the past.

This RFC's title seems very misleading however.

@varkor
Copy link
Member

varkor commented May 27, 2018

What's the status of this RFC? I've often wanted similar functionality — is the main blocker here deciding upon method names?

@aturon aturon removed their assignment Jun 3, 2018
@aturon
Copy link
Member

aturon commented Jun 3, 2018

I'm unassigning myself and nominating for libs team discussion.

@nielsle
Copy link

nielsle commented Jun 3, 2018

Just for completeness. The following works on nightly.

let pred = true;
let filtered = Some("test").filter( |_| pred );

@jonhoo
Copy link
Contributor

jonhoo commented Jun 3, 2018

@nielsle I don't think that quite covers the use-case. Specifically, consider the case where the Some is expensive to construct. You would much rather do

pred.if_true(|| make_expensive_thing())

@Dynisious
Copy link
Author

Dynisious commented Jun 14, 2018

Hey been a while since I did anything with this.
Finally got around to making a simple crate implementation

@alexcrichton
Copy link
Member

@rfcbot fcp postpone

This RFC is unfortunately a little old at this point and has languished for awhile. At this point I think it's clear that we don't have the bandwidth or motivation at this point to push the bikeshed here to completion to figure out what names these methods would have in the standard library. In the meantime there's a crate on crates.io for these methods which isn't great in terms of ergonomics but provides a good way to test out the eventual method names!

I propose we postpone this RFC to a later date when the bikeshed can be driven to completion. Otherwise we're not necessarily benefitting much from having this RFC bake in the queue.

@rfcbot
Copy link
Collaborator

rfcbot commented Aug 23, 2018

Team member @alexcrichton has proposed to postpone this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-postpone This RFC is in PFCP or FCP with a disposition to postpone it. labels Aug 23, 2018
@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Sep 5, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Sep 5, 2018

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Sep 5, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Sep 15, 2018

The final comment period, with a disposition to postpone, as per the review above, is now complete.

By the power vested in me by Rust, I hereby postpone this RFC.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. postponed RFCs that have been postponed and may be revisited at a later time. and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. disposition-postpone This RFC is in PFCP or FCP with a disposition to postpone it. labels Sep 15, 2018
@rfcbot rfcbot closed this Sep 15, 2018
@varkor
Copy link
Member

varkor commented Sep 5, 2019

I've opened a follow up RFC here: #2757.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
finished-final-comment-period The final comment period is finished for this RFC. postponed RFCs that have been postponed and may be revisited at a later time. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.