Skip to content

[sammy] eta-expansion, overloading, existentials #4101

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

Merged
merged 3 commits into from
Nov 10, 2014
Merged

Conversation

adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Nov 6, 2014

Review by @retronym, please.

While playing with Java 8 Streams from the repl,
I realized we weren't eta-expanding,
nor resolving overloading properly for SAMs.

Also, the way Java uses wildcards to represent use-site variance
stresses type inference (since it results in existentials).

Introduce wildcardExtrapolation to simplify the resulting types
(without losing precision): wildcardExtrapolation(tp) =:= tp.

For example, the MethodType given by def bla(x: (_ >: String)): (_ <: Int)
is both a subtype and a supertype of def bla(x: String): Int.

Translating http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ into Scala:

scala> import java.util.Arrays
scala> import java.util.stream.Stream
scala> import java.util.stream.IntStream
scala> val myList = Arrays.asList("a1", "a2", "b1", "c2", "c1")
myList: java.util.List[String] = [a1, a2, b1, c2, c1]

scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted.forEach(println)
C1
C2

scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted
res8: java.util.stream.Stream[?0] = java.util.stream.SortedOps$OfRef@133e7789

scala> Arrays.asList("a1", "a2", "a3").stream.findFirst.ifPresent(println)
a1

scala> Stream.of("a1", "a2", "a3").findFirst.ifPresent(println)
a1

scala> IntStream.range(1, 4).forEach(println)
<console>:37: error: object creation impossible, since method accept in trait IntConsumer of type (x$1: Int)Unit is not defined
(Note that Int does not match Any: class Int in package scala is a subclass of class Any in package scala, but method parameter types must match exactly.)
              IntStream.range(1, 4).forEach(println)
                                            ^

scala> IntStream.range(1, 4).forEach(println(_: Int)) // TODO: can we avoid this annotation?
1
2
3

scala> Arrays.stream(Array(1, 2, 3)).map(n => 2 * n + 1).average.ifPresent(println(_: Double))
5.0

scala> Stream.of("a1", "a2", "a3").map(_.substring(1)).mapToInt(_.parseInt).max.ifPresent(println(_: Int)) // whoops!
ReplGlobal.abort: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
error: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
scala.reflect.internal.FatalError: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
    at scala.reflect.internal.Reporting$class.abort(Reporting.scala:59)

scala> IntStream.range(1, 4).mapToObj(i => "a" + i).forEach(println)
a1
a2
a3

@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

/cc FYI, @Ichoran

@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

I'll investigate the failures in the REPL transcript later. Need to get back to my talk :)

@scala-jenkins scala-jenkins added this to the 2.11.5 milestone Nov 6, 2014
@gourlaysama
Copy link
Contributor

👍 Yay! Overloading is the thing that prevented SAMs from being really useful when calling into java.

BTW there was a ticket for that: SI-8310

@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

Ugh, the jenkins node I added is running an outdated version of java.

@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

Thanks for the pointer @gourlaysama! I'll include the test case -- it passes now.

@retronym
Copy link
Member

retronym commented Nov 6, 2014

I offlined them about 15 mins ago.

Playing with Java 8 Streams from the repl showed
we weren't eta-expanding, nor resolving overloading for SAMs.

Also, the way Java uses wildcards to represent use-site variance
stresses type inference past its bendiness point (due to excessive existentials).

I introduce `wildcardExtrapolation` to simplify the resulting types
(without losing precision): `wildcardExtrapolation(tp) =:= tp`.

For example, the `MethodType` given by `def bla(x: (_ >: String)): (_ <: Int)`
is both a subtype and a supertype of `def bla(x: String): Int`.

Translating http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
into Scala shows most of this works, though we have some more work to do (see near the end).

```
scala> import java.util.Arrays
scala> import java.util.stream.Stream
scala> import java.util.stream.IntStream
scala> val myList = Arrays.asList("a1", "a2", "b1", "c2", "c1")
myList: java.util.List[String] = [a1, a2, b1, c2, c1]

scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted.forEach(println)
C1
C2

scala> myList.stream.filter(_.startsWith("c")).map(_.toUpperCase).sorted
res8: java.util.stream.Stream[?0] = java.util.stream.SortedOps$OfRef@133e7789

scala> Arrays.asList("a1", "a2", "a3").stream.findFirst.ifPresent(println)
a1

scala> Stream.of("a1", "a2", "a3").findFirst.ifPresent(println)
a1

scala> IntStream.range(1, 4).forEach(println)
<console>:37: error: object creation impossible, since method accept in trait IntConsumer of type (x$1: Int)Unit is not defined
(Note that Int does not match Any: class Int in package scala is a subclass of class Any in package scala, but method parameter types must match exactly.)
              IntStream.range(1, 4).forEach(println)
                                            ^

scala> IntStream.range(1, 4).forEach(println(_: Int)) // TODO: can we avoid this annotation?
1
2
3

scala> Arrays.stream(Array(1, 2, 3)).map(n => 2 * n + 1).average.ifPresent(println(_: Double))
5.0

scala> Stream.of("a1", "a2", "a3").map(_.substring(1)).mapToInt(_.parseInt).max.ifPresent(println(_: Int)) // whoops!
ReplGlobal.abort: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
error: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
scala.reflect.internal.FatalError: Unknown type: <error>, <error> [class scala.reflect.internal.Types$ErrorType$, class scala.reflect.internal.Types$ErrorType$] TypeRef? false
	at scala.reflect.internal.Reporting$class.abort(Reporting.scala:59)

scala> IntStream.range(1, 4).mapToObj(i => "a" + i).forEach(println)
a1
a2
a3

```
@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

Thanks -- I push --forced to address review feedback (include SI-8310 test case).

@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 6, 2014

Need to update that check file. The new error message is correct, though suboptimal...

val tp1 = normalize(tp)

( (tp1 weak_<:< pt)
|| isCoercible(tp1, pt)
|| isCompatibleByName(tp, pt)
|| isCompatibleSam(tp, pt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where we predicate this on -Xexperimental. I believe we should. I'd also be interested in performance impact of this; we should have a fast path for non-SAMs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, I see samOf checks the settings. That will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it's easier to flip the switch if it's only tested in one central spot.

Don't naively derive types for the single method's signature
from the provided function's type, as it may be a subtype
of the method's MethodType.

Instead, once the sam class type is fully defined, determine
the sam's info as seen from the class's type, and use those
to generate the correct override.

```
scala> Arrays.stream(Array(1, 2, 3)).map(n => 2 * n + 1).average.ifPresent(println)
5.0

scala> IntStream.range(1, 4).forEach(println)
1
2
3
```

Also, minimal error reporting

Can't figure out how to do it properly, but some reporting
is better than crashing. Right? Test case that illustrates
necessity of the clumsy stop gap `if (block exists (_.isErroneous))`
enclosed as `sammy_error_exist_no_crash`

added TODO for repeated and by-name params
@adriaanm
Copy link
Contributor Author

adriaanm commented Nov 7, 2014

Generate correct trees to refer to repeated params using `gen.paramToArg`.
Based on retronym's review feedback.
@retronym
Copy link
Member

retronym commented Nov 9, 2014

LGTM

lrytz added a commit that referenced this pull request Nov 10, 2014
[sammy] eta-expansion, overloading, existentials
@lrytz lrytz merged commit 5a7875f into scala:2.11.x Nov 10, 2014
@@ -2701,6 +2725,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
* and have the instantiation of the first occurrence propagate to the rest of the block.
*
* TODO: by-name params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adriaanm, by this you mean automatically applying the SAM implementation's abstract method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you're referring to, but I meant that f(println("!")) should behave the same as f.accept(println("!")) below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it now, thanks @adriaanm!

@VladUreche
Copy link
Contributor

Cool patch! I'm super excited about this making its way to 2.11.5 👍

ilinum added a commit to ilinum/intellij-scala that referenced this pull request Jul 29, 2015
In some cases existential bounds can be simplified without losing precision.

For example:
trait Blargle[T] { def compare(a: T, b: T): Int }

trait Test {
  def foo(a: Blargle[_ >: String]): Int
}

can be simplified to:

trait Test {
  def foo(a: Blargle[String]): Int
}

see: scala/scala#4101
#SCL8956 Fixed
ilinum added a commit to ilinum/intellij-scala that referenced this pull request Jul 31, 2015
In some cases existential bounds can be simplified without losing precision.

For example:
trait Blargle[T] { def compare(a: T, b: T): Int }

trait Test {
  def foo(a: Blargle[_ >: String]): Int
}

can be simplified to:

trait Test {
  def foo(a: Blargle[String]): Int
}

see: scala/scala#4101
#SCL8956 Fixed
ilinum added a commit to ilinum/intellij-scala that referenced this pull request Jul 31, 2015
In some cases existential bounds can be simplified without losing precision.

For example:
trait Blargle[T] { def compare(a: T, b: T): Int }

trait Test {
  def foo(a: Blargle[_ >: String]): Int
}

can be simplified to:

trait Test {
  def foo(a: Blargle[String]): Int
}

see: scala/scala#4101
#SCL8956 Fixed
@adriaanm adriaanm deleted the sam-ex branch August 5, 2015 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants