-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Ad-hoc operator overloading #399
Conversation
How do multi-method operators like slice work? |
Each method gets its own |
How will I as a consumer of a library be able to easily find out if I can use |
Can slice be partially implemented, then? Only implement [..n] or whatever? |
Typically you will still implement the traits from
Yeah. |
I like this idea. Currently I think it is bad that we describe operators using traits: people will try to use those operator traits as trait bounds, which in my opinion is a bad idea as the operator could mean anything: for numbers it’s addition, but for strings it’s concatenation. Using I also like the way that slicing could potentially be split up with this. Strings really shouldn’t implement I’d also like to make the suggestion that the |
I agree with @P1start's final point about making it use the actual operator, to at least leave open the possibility of arbitrary operators. |
How do you write things like generic ranges in this case? Currently pub fn range<A: Add<A, A> + PartialOrd + Clone + One>(start: A, stop: A) -> Range<A> There is a nice and clean bound which only requests what is absolutely needed for the range. How will this function look with ad-hoc operator overloading? The reason why it works in C++, for example, because templates there are just raw substitutions. But I don't think we want to introduce C++-like templates into Rust. BTW, Haskell is not a valid example of ad-hoc overloading. You can't overload |
Thinking about this, it makes a lot of sense. After all, when you write "x.foo(y)", the compiler doesn't lookup a Foo trait and then call its foo method, but rather it just performs method lookup for foo (and obviously this is a good thing). There doesn't seem to be any reason for "x + y" to work differently. Traits with operators are still possible and would work. |
```rust | ||
trait MyTrait { | ||
#[operator="add"] | ||
fn add(&self, rhs: &uint) -> uint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use a C++-ish syntax like this:
fn op +(&self, rhs: &uint) -> uint;
or a Scala-ish notation like this:
fn +(&self, rhs: &uint) -> uint;
or even this daring syntax (not sure if this is unambiguous and parsable):
fn (&self + rhs: &uint) -> uint;
One could also imagine allowing any sequence of non-[A-Za-z0-9] characters (with exceptions to avoid conflicts) as an operator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a possibility. I went with a more conservative choice for implementation ease.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without committing to supporting this RFC, the Haskell style fn (+)(lhs: T, rhs: U) -> V
might make more sense, seeing as binary operators are called in a different way to regular functions.
This RFC would be more convincing if it included examples of useful patterns this would allow that can't be created using today's system. Personally I'm not really aware of many cases, but I could be easily convinced if I saw code that was much nicer to write or work with under this RFC. |
@netvl in some ways this is a bug; |
We could say that (This is not a comment about this RFC specifically, just pointing out that that reasoning doesn't necessarily apply.) |
Yes, but then you don’t get the nice Basically, I’m trying to say that we are ‘abusing’ traits by making |
Why would you want to use |
This would make the indexing and slicing operators actually useful without having to wait for HKT, the current method signature of
// Self = Mat<T>
fn index<'a>(&'a self, row: &uint) -> Row<'a, T> { .. }
let row = mat[0];
let elem = mat[1][2];
// Self = Mat<T>
fn slice_or_fail<'a>(&'a self, start: (uint, uint), end: (uint, uint)) -> View<'a, T> { .. }
assert_eq!(mat[(1, 2) .. (3, 4)].size(), (2, 2)); None of that is possible with the current About the proposal, I got two questions:
// crate foo
trait CharIndex {
#[operator="[]"]
fn index(&self, pos: uint) -> char;
}
impl CharIndex for String {}
impl Foo {
#[operator="[]"]
fn index(&self, index: uint) -> Bar { .. }
}
trait MyIndex {
#[operator="[]"]
fn index(&self, index: uint) -> Baz;
}
// is it possible to overload the operator again?
impl MyIndex for Foo { .. }
foo[0] // what's the return value: Bar or Baz? Other thoughts:
|
Yeah, that might be a good idea. My only concern is the arbitrariness of the
Yes, I'm aware this is not practical to take advantage of in Haskell. Nevertheless, if you want to have a hard time (i.e. only use your non- @japaric in all cases those would act like normal methods. E.g. you will be able to add new operators to types outside the crate (you'll have to use your own trait or a wrapper type). In the second case, it'd be a conflict that you'll have to two options of resolving:
|
Re usecases: some people would also like to be able to unsafely index or +/- a ptr. This RFC makes me feel some feels. I think this is the sort of solution that makes the most sense long term. I'm not sure if it would be appropriate for 1.0, though. Lots of work to get this going, I imagine. There will need to be some checks for arity involved (I don't recall seeing this in the proposal?). If actual symbols are used, then something will need to be done to handle arity-overloaded operators. "-" being the most obvious one. For those interested in generalizing this to a future with "any operator", how would this be handled? Would all operators be candidates for unary and binary forms? Or perhaps we could have a syntax like It might also make sense to put some sanity constraints on the ownership of the LHS. For instance, |
I think also allowing |
This shouldn't cause new type-checking problems – Anyway, it will be interesting to know how this handles |
Ah ok, the overloading and methods-only restriction go together. I am not sure how I feel about exploiting identifier ambiguities in this way however---the system is analogous to an unhygienic macro. But yes, it does get the job done. |
And |
I am sympathetic to this but I think that Haskell is not a valid comparison, because being able to overload I believe this can be added backwards compatibly post 1.0, so I'll mark this as postponed. |
As a counterexample, Haskell and Scala use |
I'm glad to see something like this being proposed. The current approach, while conceptually simple, just does not seem to scale very well at all.
I agree with this 100%. I have a semigroup library where having an operator like |
@darinmorrison, that's a perfectly reasonable multiplication, in particular, it is associative; I'm not concerned about arithmetic operations mainly mathematical convention. I'm afraid you'll need to be more specific about what the contortions are because that doesn't seem outrageous to me. |
Sorry, I should have been more specific. In particular, it's a nuisance to have to wrap everything in I suppose there are ways to work around this using macros (see here) but that seems a bit unsatisfying, not to mention complicated. |
I'm not sure mathematical convention is a good measure because it varies too much depending on your perspective. For instance, |
@darinmorrison Have you seen my num-rs library? It shows one way to do it using the current operator traits. |
@bjz I hadn't seen that. Looks very nice! I'll have to take a closer look, thanks. |
@bjz Ah, having looked at it closer now, I think I see what you mean. This is actually how I was doing |
Rendered.
This RFC is in many ways an alternative to #392.