dart-lang / language

Design of the Dart language
Other
2.61k stars 200 forks source link

type parameter in operator overloading #1044

Open dhaval15 opened 4 years ago

dhaval15 commented 4 years ago

Dart does not support adding types in operator overloading.

void operator |<T>(T value);

It would be great if it is supported for function currying, piping using extension and operator overloading.

lrhn commented 4 years ago

The biggest issue with generic operators is that you can't pass them explicitly. It would be like a generic function where you could only use the type argument that was inferred, with no way to override it.

It's not completely untenable. It makes sense to use generics as a kind of existential type predicate. It's not particularly practical when you only use the type variable once, like in the example given here. It's more convenient when it occurs more than once, like:

extension FutX<T> on Future<T> {
  Future<R> operator>><R>(FutureOr<R> Function(T) handler) => this.then<R>(handler);
}
...
   future >> ((x) => x + 1)  // wohoo

(Getters and setters are even harder, and less useful, to make generic that way because they only have one moving part that you can type).

mateusfccp commented 4 years ago

Well, it would be even nicer if there was no distinction between operators and functions, but I know there's no viability in Dart.

lrhn commented 4 years ago

The distinction is mainly syntactical.

The method operator+ is invoked by a + b, and that's the only way to call it, and there is no way to create a tear-off. Therefore the operator method needs to be callable with exactly one argument, and there is no use in allowing optional parameters or type parameters. Apart from that, operators are really just normal methods.

If we introduced a way to call operators directly, say a.+(b), then we could allow tearoffs (a.+), optional parameters a.+(b, mod: n) or type parameters a.+<num>(b). Type parameters is the one thing we could allow without adding a way to pass them explicitly, because the language has a way to pass them implicitly (type inference). In general, I prefer that anything inference does, can also be written explicitly (including instantiated generic method tear-offs - #125).

The real question here is how much of this is actually useful and therefore worth the effort. Allowing optional parameters seems spurious, but generics and a way to do tear-off and explicit invocation could be useful.

There are parsing issues. Since both < and << are declarable operators, x.<<y can get tricky to parse. Probably not impossible, but needing too far a look-ahead to know what you are doing, can easily make the parser more fragile and harder to add other features to.

munificent commented 4 years ago

I was thinking that extensions might let you accomplish this:

class C {}

extension<T> on C {
  T operator |(T value) {
    print("pipe $T");
    return value;
  }
}

main() {
  var c = C();
  c | true;
  c | 123;
}

But this prints:

pipe dynamic
pipe dynamic

So I guess type inference on extensions doesn't to upwards inference based on the RHS of an operator?

leafpetersen commented 4 years ago

@munificent the type of the arguments are not taken into account when resolving the generic type parameters of the extension. This was a deliberate choice, based on design discussion at the time. I'm not sure if it works for this use case, but you can change your code to be extension<T> on T ... and T will be resolved based on the receiver type (but not the argument type).

MelbourneDeveloper commented 9 months ago

I would like to resurrect this one. I am working on various Monads for Dart, and they are quite verbose without operator overloading. It would be nice to have both generic type arguments and the ability to override arbitrary symbols. One issue is that Dart gets too brackety when you call extensions inside extensions and so on.

The main issue is with the equivalent of Haskell's fmap function. It can return an arbitrary type of Monad, but without generic type arguments in operator overloading, it only returns dynamic, which is dangerous.

Will provide examples soon.

@munificent

dgreensp commented 2 months ago

I just ran into this. I have two types, Bar extends Foo, and I wanted bar + bar to be Bar but bar + foo to be Foo, similar to how int + int is int but int + num is num. I was going to have the return type of + on Bar be the type of its argument.

lrhn commented 2 months ago

Indeed, that won't work. You could (maybe) get around it with a generic method like:

abstract class Foo {
  Foo add<T extends Foo>(T other);
}

abstract class Bar extends Foo {
  T add<T extends Foo>(T other);
}

abstract class Baz extends Foo {
  Baz add<T extends Foo>(T other);
}

but without generic operators, you can't do that with +.