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

Custom operators #818

Closed
iluuu1994 opened this issue Feb 10, 2015 · 23 comments
Closed

Custom operators #818

iluuu1994 opened this issue Feb 10, 2015 · 23 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@iluuu1994
Copy link

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:

  • Precedence
  • Left or right asociativity
  • Prefix, infix, or postfix

Here's how Rust could look defining a custom null coalescing operator:

op ?? {
    assoc: left,
    prec: 100,
}

infx fn ??<T>(l: Option<T>, r: T) -> T {
    match l {
        Some(s) => s,
        None => r,
    }
}

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:

Btw Rust is super awesome :D 🤘

Fantastic work guys

@glaebhoerl
Copy link
Contributor

It would make sense to title this issue "custom operators" or "user-defined operators" or something of that nature instead of "operator overloading".

@iluuu1994 iluuu1994 changed the title Operator overloading Custom operators Feb 10, 2015
@cameron-martin
Copy link

Is this being considered at all?

@KalitaAlexey
Copy link

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

@Centril
Copy link
Contributor

Centril commented Apr 13, 2016

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

@ticki
Copy link
Contributor

ticki commented Apr 13, 2016

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.

@alilleybrinker
Copy link

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.

@davesque
Copy link

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.

@ticki
Copy link
Contributor

ticki commented Feb 20, 2017

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.

@burdges
Copy link

burdges commented Feb 21, 2017

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:

  1. 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!

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

  3. A good REPL would let people experience using Rust in a more dynamic way.

@davesque
Copy link

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

@glaebhoerl
Copy link
Contributor

Method syntax is infix invocation.

@iluuu1994
Copy link
Author

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.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 23, 2018
@typesanitizer
Copy link

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:

  1. 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".

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

  3. I feel that there are two related but distinct sides to the discussion here -- libraries and applications.

    1. In commonly used library crates, it would be strange if there were a large number of arcane operators. However, judicious use can be a net positive. For example, Conduit has 1 main operator .| that describes pipelines, analogous to the shell pipe. (It has other operators too but they're deprecated).
    2. In applications, if you're writing any kind of EDSL, not having custom operators with user-defined fixities can make writing simple code unnecessarily verbose.
  4. 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.

@blackhole89
Copy link

blackhole89 commented May 17, 2019

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?)

@SOF3
Copy link

SOF3 commented Sep 15, 2019

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

@nicowilliams
Copy link

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.

@SOF3
Copy link

SOF3 commented Jul 14, 2020

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.

@00shiv
Copy link

00shiv commented Dec 28, 2020

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.

@SOF3
Copy link

SOF3 commented Dec 29, 2020

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

@martial-plains
Copy link

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.

@SOF3
Copy link

SOF3 commented Feb 17, 2023

maybe we could implement something equivalent if #![custom_attribute] on modules was possible

#![binary_op(PartialEq::ne = <>)]

fn main() {
    println("{}", 1 <> 2); // true
}

@EarthCitizen
Copy link

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

@SOF3
Copy link

SOF3 commented Apr 5, 2023

@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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests