Skip to content

RFC: Imply Option #2180

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

Closed
wants to merge 10 commits into from
Closed
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions text/0000-imply-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
- Feature Name: imply-option
- Start Date: 2017-10-18
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

This is an RFC to reduce common boiler plate code when making use of the `Option` type, providing two functions
```
/// Construct `Some(T)`, conditionally on a boolean.
fn Option::<T>::on_pred(bool, T);
/// A lazy equivalent of `on_pred`.
fn Option::<F: FnOnce() -> T>::lazy_pred(bool, F);
```
Similar in intention and motivation to the `Try` trait for `Result`.

# Motivation
[motivation]: #motivation

This addition will increase the legibility of code segments and assist in defining the thought processes and motivations of programmers through code. The use cases of this addition are problems which are expressable in the following predicate form:
```
P(x) : Predicate on `x`.
F(y) : Function on `y`
P(x) -> F(y)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a new proposed syntax? It sounds like you're introducing a mathematical syntax which you're going to use later to talk about something else.

Copy link
Author

Choose a reason for hiding this comment

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

This is not a proposed syntax. It is predicate logic which is the syntax of Boolean algebra. I am using it as a tool to formally define my solution rather than only waving my hands around and providing examples.

```
Or the following Rust pseudocode:
```
if P(x) {
Some(F(y))
} else {
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 fashion through their code.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

The `Option` type is 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.

let x = 0;

if x == 0 {
Some(x)
} else {
None
}
```
However only the `if` branch of this code segment is the important part we're concerned about in our code:
```
if x == 0 {
Some(x)
}
```
But the `else` branch is required for returning `None` value if `x == 0` evaluates to false.
Fortunately Rusts `Option` type has functionality to 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.

This suggests rust already has this functionality and that at this point, the point of this proposal isn't clear at all.

Copy link
Author

Choose a reason for hiding this comment

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

If you read the Template for RFCs provided in the repository, the Guide-level Explanation is meant to be an explanation by example as if it already existed in the language and it was being taught to someone who is new to Rust, similar in format to rust-by-example.

```
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.

let x = 0;

Option::on_pred(x == 0, x)
```
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.
Have you spotted the possible issue with this solution introduces however? What about this code:
```
Option::on_pred(false, foo())
```
The above line of code will always return `None` and always throw away the result of `foo()` wasting our precious computing power every time our code needs to return `None`.
Rust has thought ahead of this problem though:
Copy link
Contributor

Choose a reason for hiding this comment

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

This still suggests rust is perfect already and already includes this fancy method below which solves everything. If the proposal for this RFC is to add 2 methods, please state that in the summary with a single line description for each.

Copy link
Author

Choose a reason for hiding this comment

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

Again that is the format which is requested in the RFC template of rust-lang/rfcs.

```
Option::lazy_pred(false, foo)
```
`Option`s `lazy_pred` function leverages lazy evaluation by taking a function pointer as its second argument. If its first argument evaluates to `true` it will return `Some(foo())` but if its first argument is `false` it returns `None` without having to run `foo`. This solves the problem presented in our earlier example without sacrificing the advantages it gave us.
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a feeling with optimizations on Rust would be able to figure this out and not need this method at all.
If you switch it from true to false the code generated completely changes: https://godbolt.org/g/o2WE4h

I could be wrong but this seems like something LLVM will handle fine without us needing the lazy version of this function.

Copy link
Member

Choose a reason for hiding this comment

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

This is more about side-effects - it's pretty much.map(|f| f()).

Copy link
Member

Choose a reason for hiding this comment

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

Or .map(|_| {...}) on Option<()>.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay that makes more sense then.


# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

This addition is in essence an implementation of the "implies" or "->" operation from First-order logic.
In predicate logic, for those unfamiliar, the "->" operation has the following truth table:
Copy link
Contributor

Choose a reason for hiding this comment

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

"First-order logic" (FOL) is the more common term.

Choose a reason for hiding this comment

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

This may seem a pedantic point, but the proposed operation has actually very little to do with first-order logic. Predicate logic (first-order or otherwise) generally concerns itself with taking an expression of a certain form (a sentence) and a complex object which defines the semantics of such expressions (a model) and obtaining a truth value. (Generalising this a bit, you could also ask which truth values result from which variable assignments in a formula with free variables. But that's it.)

What is proposed here is, in a sense, an opposite operation: it takes a truth value and returns an object of some other type. (It could be likened to an Iverson bracket, but even that is a somewhat strained analogy.) No such thing exists in first-order logic: implication takes two truth values and returns a truth value, and every predicate and relation takes a tuple of objects and returns a truth value. There's no going from a truth value to some other thing in predicate logic.

I'd remove all mentions of 'predicate logic' from this proposal. It isn't actually helpful even for those who are familiar with it.

```
| x | y | x -> y |
| F | F | T |
| F | T | T |
| T | F | F |
| T | T | T |
```
The Rust addition this RFC suggests can be encapsulated as "If `x` is `true`, I care about the value of `y`; else I do not care about the value of `y`." or:
```
| x | x -> y |
| F | None |
| T | Some(y)|
```
This RFCs initial proposal for how this addition could be implemented is:
```
impl<T: Sized> Option<T> {
/// A straight forward implementation of the `implies` operation for predicate logic.
fn on_pred(pred: bool, value: T) -> Self {
if pred {
Some(value)
} else {
None
}
}
/// A lazy implementation of the `implies` operation when necessary.
fn lazy_pred<F>(pred: bool, func: F) -> Self
where F: FnOnce() -> T {
if pred {
Some(func())
} else {
None
}
}
}
```
This implementation covers the use cases proposed in the earlier examples and any others of similar form without any external dependencies; this should make the implementation stable as Rust continues to 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 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.
Other designs which have been considered are:
- Not including the `on_pred` function. However this adds additional boiler plate code to make use of the functionality when passing in a value directly:
```
let x = 0;

//Option::on_pred(true, x)
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 equivalent `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, 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 adequately cover syntax support for both the `on_pred` and `lazy_pred` functions in a meaningful manner and removing either one is disadvantageous as discussed above.

# Unresolved questions
[unresolved]: #unresolved-questions

Through the RFC process I hope to qualify:
- That this is first a problem which does affect other Rust programmers.
- That my proposed solution would meaningfully improve the experience of other programmers in Rust.
- That my proposed implementation cannot be further optimised or stabilised.
As mentioned under the [alternatives] section syntax support of this feature is a possibility in future but I feel is outside the scope of this RFC before the implementation is stabilised in Rust and a meaningful syntax for this feature is yet to be determined.