language icon indicating copy to clipboard operation
language copied to clipboard

type parameter in operator overloading

Open dhaval15 opened this issue 4 years ago • 8 comments

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.

dhaval15 avatar Jun 23 '20 10:06 dhaval15

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

lrhn avatar Jun 23 '20 22:06 lrhn

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

mateusfccp avatar Jun 23 '20 22:06 mateusfccp

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.

lrhn avatar Jun 25 '20 08:06 lrhn

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?

munificent avatar Jun 29 '20 17:06 munificent

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

leafpetersen avatar Jun 30 '20 00:06 leafpetersen

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

MelbourneDeveloper avatar Oct 11 '23 20:10 MelbourneDeveloper

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.

dgreensp avatar Apr 26 '24 14:04 dgreensp

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

lrhn avatar Apr 26 '24 14:04 lrhn