Skip to content

use min/max instead of LUB/GLB in strong mode inference? #27917

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

Closed
jmesserly opened this issue Nov 28, 2016 · 7 comments
Closed

use min/max instead of LUB/GLB in strong mode inference? #27917

jmesserly opened this issue Nov 28, 2016 · 7 comments
Labels
legacy-area-analyzer Use area-devexp instead. P2 A bug or feature request we're likely to work on type-enhancement A request for a change that isn't a bug

Comments

@jmesserly
Copy link

Strong mode inference for generics gathers constraints on the type (lower and upper bounds), and uses that to find a type that satisfies the bounds (in practice, it will pick either the lower or the upper). Right now, these bounds are computed using LUB and GLB as appropriate to combine constraints. But that causes us to synthesize a type that the user did not write down anywhere, not to mention depending on the LUB/GLB functions.

I tried a switch to min/max but it causes a lot of churn in tests, so I'd like to handle this as a separate issue from our other inference changes.

@jmesserly
Copy link
Author

Credit goes to @leafpetersen for this. I suspect it's a good change but from my quick test seems a bit more disruptive than expected. That said, our tests are likely pushing on it more than real code will.

@bwilkerson bwilkerson added P2 A bug or feature request we're likely to work on type-enhancement A request for a change that isn't a bug labels Nov 29, 2016
@jmesserly
Copy link
Author

@nex3 found an interesting example of where this would be useful. Something like:

expect<T>(T value, Matcher<T> matcher /*...*/) { /*...*/ }

if I wrote code like this:

  test('confused', () {
    expect(42, isTrue);
    expect(123, equals(456.0));
  });

... those could be flagged as inference failures.

@nex3
Copy link
Member

nex3 commented Jan 19, 2017

See also dart-lang/test#2352.

@MichaelRFairhurst
Copy link
Contributor

MichaelRFairhurst commented Jan 19, 2017

In my experience, there is almost an expectation that generic methods require exact matches. In fact, when you consider that most generic methods are emulating functional languages like haskell, and that in most of those languages the inferencing scheme allows at most let-bound polymorphism but usually no polymorphism at all, it makes sense to be completely strict on generic methods and let the user satisfy the type checker with casts.

There are times where polymorpic inference for T is just fine, for instance, in fold.

  // List<T>.foldl<A>(A f(A acc, T item), A acc) 
  <int>[].foldl((x, y) => x + y), "A string accumumlator!");

When you use a string accumulator, it sets A to the LUB of "Object", and then "+" fails, so no surprises here. When you think about why, its because Y is used both covariantly and contravariantly. That's right, Y is an exact type. At least, it's constrained to be a low-enough type to pass checks in the function body.

So maybe its best to not use LUB at all, but require an exact match, like this example will.

To get deeper down the rabbit hole, in expect, T is covariant only (well, Matcher<T> shouldn't be covariant, at best it maybe could be contravariant, but yeah), and it has these problems.

Here's a contravariant only example.

  // Sink<T> createAlternatingSink<T>(void consumerA(T), void consumerB<T>)
  alternateConsumers((int x) => ..., (String y) => ...)

Here, T will be inferred as Object, and int -> void is not a subtype of object -> void, so its rejected too, no surprises here. One lesson here, in addition to the case I'm trying to build toward, is that GLB seems like a perfectly fine constraint.

So it might be best to use exact type matches everywhere when inferring a type, not using LUB or GLB at all. You can always write expect<num>(1, equals(2.5)) or expect(1 as num, equals<num>(2.5))

And maybe, this is where I'm getting crazier, its best to use LUB only where there is no contravariant usage of the type. Because it essentially means, there's no guessable floor, and we're likely to go to Object with no indication that Object is actually suitable.

@jmesserly
Copy link
Author

C# requires an exact match, FWIW.

@natebosch
Copy link
Member

Has anything changed here since the last update?
Is anything likely to change here?

@leafpetersen
Copy link
Member

We should close this. It's not happening for Dart 2 - very breaking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
legacy-area-analyzer Use area-devexp instead. P2 A bug or feature request we're likely to work on type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

6 participants