Closed iluuu1994 closed 7 years ago
It would make sense to title this issue "custom operators" or "user-defined operators" or something of that nature instead of "operator overloading".
Is this being considered at all?
@cameron-martin Could you provide example when you need custom operator and simple function couldn't help you? I am against custom operators because it is complex.
The main problem with custom operators, which is quickly experienced if you've written any haskell is that the symbols included in the operator identifier does not usually inform the meaning of what the operator does, unless it is picked very judiciously. Standard operators used in mathematics are so often used that everyone knows their meaning instantly. A function which has its identifier written in English conveys the meaning of what it does much more clearly.
Instead of adding custom operators, another beneficial idea would be to allow infix calls to functions like so:
a `dot` b
to convey scalar product of two vectors a and b.
Which is converted to
a.dot( b )
or, depending on the function:
dot( a, b )
This also has the benefit of being similar to another language (haskell).
I have a strong dislike for arbitrary user-defined operators. Essentially, you end up with each library providing an entirely different language. People quickly abuse this kind of stuff because it "seems nice at first", but it gets a mess for the library user. Parsing it is pretty much impossible too.
It is worth noting that Rust already got operator overloading, which covers the majority of cases.
Fantastic work guys
... and gals.
As an example of what sort of excess can occur with user-defined operators, look at Haskell's Lens library. It's a fantastically useful library with an complex and opaque sub-language of special operators in it. Attempting to read Lens code without the operator reference open is a task only for the library's most experienced users. Rust already has complex syntax. I don't think more complexity is a good idea.
The notion of infix operators is interesting, though I am not sure how much they get you over methods. My general instinct says to leave the syntactical complexity alone unless you have a good reason.
I'll have to put in my vote against this idea. Building on @AndrewBrinker's comment, I feel Haskell has demonstrated exactly why this kind of feature usually doesn't belong in a language. The idiom of custom operator use in Haskell is a barrier to entry, hands down. I've often wondered to myself what a language with exactly the same features as Haskell (but minus the annoying and opaque syntax) would look like and if it wouldn't gain broader acceptance. If this kind of thing ever does find its way into Rust, it would have to be in the most sparing and sober way possible.
Haskell went through so much pain, with the only advantage of having a mostly useless feature. In fact, it tend to be abused and makes everything a mess.
The infix function is more sane, but I'm not sure if it is needed enough to be worth adding.
I think custom syntax can help with mathematics, well crypto crates love #![allow(non_snake_case)]
, but custom operators rarely help much.
Instead I'd look more towards the units crate which uses wrapper structs heavily. I think more important tools include:
It'd rock if side effects could be exposed well enough by a bignum library that DivRem or similar can be optimized correctly from separate /
and %
calls. You do not want those all those pretty binary operators killing performance!
We do not make up many so many operators in mathematics, but instead dance between several different objects in which we envision an element living. Arguably that perception shift corresponds best to applying wrapper structs that change how existing operators apply. I'd think painless delegation would let map
change these wrapper structs more easily.
A good REPL would let people experience using Rust in a more dynamic way.
@ticki @Centril While I don't like custom operators, I actually agree that infix invocation could be useful. I've even enjoyed using languages that also have prefix and suffix invocation (as with Haskell's $
operator and Mathematica's @
and //
operators). But I think it would need to be an experimental feature and would need to go through an extensive trial period to prove that abuse of it would not become idiomatic. Since Rust (IMHO) already has a lot of syntax, it's a bit risky introducing features like this.
Method syntax is infix invocation.
I'm closing this RFC as most people seem to be aganist this idea. I admit that in general the usefulness of this feature is limited. I was writing parser combinators which is one of the few times custom operators could be useful.
Just ran into this issue right now. I'm working on an application and I wanted to have a trait called PanickingAdd
which panics on overflow in both debug and release mode (I'm working with relatively small numbers). It would be nice if I could write a +!
instead of panicking_add
everywhere. Or +?
instead of checked_add
. Sure I could overload +
for my own types to always panic on overflow but then the semantics would not be consistent with that of the standard library (panic in debug and wrap in release).
Addressing some of the points made here:
Purescript has a solution where operators are only allowed as aliases for functions. This solves the problem of "I want to use this library's API but I don't like their use of operators".
The lens
library in Haskell uses a lot of operators: Not everyone likes this and people who don't like to use them will not use them.
I feel that there are two related but distinct sides to the discussion here -- libraries and applications.
.|
that describes pipelines, analogous to the shell pipe. (It has other operators too but they're deprecated).Operators can serve as a barrier to entry - Perhaps one can adopt a policy that the standard library and nursery crates do not add operators without RFCs on a case-by-case basis.
To add another voice in favour and a use case that is not parser combinators, I have been involved in a number of ML-family projects that make extensive use of a "pipeline" operator |>
(left-associative, low precedence, and having type t->(t->s)->s
). This allows expressing code that, in C-family idiom, would take a shape like fn4(a,b,c,fn3(d,fn2(e,f,fn1(g,h,input))))
in the arguably much more legible form input |> fn1 g h |> fn2 e f |> fn3 d |> fn4 a b c
.
Of course, its utility is greatly diminished when there is no good language support for binding all but one of a function's arguments (in the ML family, this is just achieved by ubiquitous currying and careful API design putting the argument that most likely wants to be "piped through" last), but I think it is a great example of a case where custom operators enable API design that palpably improves code quality.
(Regarding the lens
counterexample: The lenses library is admittedly terrible in that regard, but I'm not sure if this is really a consequence of custom operators rather than the intrinsic awkwardness of implementing lenses in a language like Haskell. Would an alternative version that only uses normal functions actually be better?)
A point not mentioned here is that macro-by-example (macro_rules!
) can cover the case for specific context. If a general purpose crate is introducing a new operator, it is modifying the language and should not be done in a crate (especially considering how likely it is for multiple such crates to collide).
On the other hand, if a discipline-specific crate wants to introduce a new operator, its relevant code is usually restricted within specific blocks. So for crates that want to provide an API akin o a specific area, a preprocessing macro would be nice. (For example, someone wrote a crate that replaces Rust's {}
with off-side syntax, but everyone knows how bad such a drastic change to the language would be like, but it totally makes sense to do this in a specific block if for whatever valid reason someone wants to write inline python code).
@blackhole89's concern about the |>
operator sounds like something that can be alternatively done in an and_then
syntax (especially if they are futures).
In Haskell, custom infix operators are used everywhere. It's true, after a while there are so many it's hard to keep track of them mnemonically, but still, being able to add new operators is often incredibly useful. As long as it is clear what crate/module a custom operator comes from, readers can find the docs and figure out the semantics of any unusual, non-obvious custom operators.
I strongly recommend reopening this RFC.
In Haskell, custom infix operators are used everywhere. It's true, after a while there are so many it's hard to keep track of them mnemonically, but still, being able to add new operators is often incredibly useful. As long as it is clear what crate/module a custom operator comes from, readers can find the docs and figure out the semantics of any unusual, non-obvious custom operators.
I strongly recommend reopening this RFC.
Do you have a particular use case not mentioned above? If no new point is made, we would end up repeating the conversation above.
I strongly recommend re-opening this RFC too. In specialized fields custom operators are well-understood within that community already. Replacing these with long names would reduce readability. Yes, newbies will have to learn this new "language" but that is true if you look at any highly specialized field. Language designers should not circumscribe this needlessly. If a particular user wants to give an "English name" to a particular operator nothing in Rust prevents them from doing so. But the opposite is not true currently.
I strongly recommend re-opening this RFC too. In specialized fields custom operators are well-understood within that community already. Replacing these with long names would reduce readability. Yes, newbies will have to learn this new "language" but that is true if you look at any highly specialized field. Language designers should not circumscribe this needlessly. If a particular user wants to give an "English name" to a particular operator nothing in Rust prevents them from doing so. But the opposite is not true currently.
How do macros not solve your problem? Please read the discussion above. In particular:
If a discipline-specific crate wants to introduce a new operator, its relevant code is usually restricted within specific blocks. So for crates that want to provide an API akin o a specific area, a preprocessing macro would be nice. (For example, someone wrote a crate that replaces Rust's
{}
with off-side syntax, but everyone knows how bad such a drastic change to the language would be like, but it totally makes sense to do this in a specific block if for whatever valid reason someone wants to write inline python code).
How about having the custom operators be implemented using traits just like the operators in currently in Rust? That way, if there is any collision between crates you can go back to the trait method that the custom operator was basically syntax sugar for.
For example:
#[doc(alias = "|>")]
pub trait ForwardPipe<Rhs = Self> {
/// The resulting type after applying the `|>` operator.
type Output;
/// Performs the `|>` operation.
///
/// # Example
///
/// ```
/// fn print(message: &str) {
/// println!("{}", message);
/// }
///
/// // "Hello World" will be passed as a parameter to the print function
/// "Hello World" |> print;
///
/// // "Goodbye World" will be passed as a parameter to the print function
/// ForwardPipe::forward_pipe("Goodbye World");
/// ```
#[must_use]
fn forward_pipe(self, rhs: Rhs) -> Self::Output {
...
}
}
Also, with this solution you can see the documentation on how to use the operator if the crate implemented it. So there there would be meaning to the operator than just a symbol. Named functions are great and easy to understand. They should be first consideration when having custom operators in a language but in some cases symbols may be even better.
maybe we could implement something equivalent if #![custom_attribute]
on modules was possible
#![binary_op(PartialEq::ne = <>)]
fn main() {
println("{}", 1 <> 2); // true
}
With the ability to override existing operators, *
, +
, etc., can become whatever you want it to be. In this case, you would still need to refer to the documentation of the crate to understand what this does. So, I am not sure that needing to read documentation to understand the operators in a crate is a compelling argument against custom operators. Nor does limiting to a known set of operators prevent unclear code and "messiness".
@EarthCitizen the impact of overriding existing operators only propagates to the resultant type and inference of the operator, but custom operators are way more complex than that, involving arbitrary precedence and associativity rules, unclear unary/binary-ness, etc., which can cause way more problems than just reading the documentation, especially for editors that are unable to resolve the custom operators (and yes, even rust-analyzer still constantly runs into macro resolution issues for me all the time).
Original issue
There's some controversy over custom operators and them being overused in cases that don't really make sense and how they can be more confusing than helpful. I personally love them and think that they, when used wisely, can really make certain APIs much more appealing.
Rust allows you to overload a few basic operators using traits defined in the standard library. That's great but you're very limited to what you can actually do with them.
I'd like to point you to Swift here. They have solved this problem rather nicely. You can implement operator functions and define some properties on them, like:
Here's how Rust could look defining a custom null coalescing operator:
I haven't given too much thought to the example above, it's probably suboptimal and doesn't play too well with the existing syntax. So it's just an example.
Basically, I'm interested in hearing what other people would think of more generic operator overloading functionality in Rust.
As I said before:
Fantastic work guys