Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Covariant "lookup" for contravariant typeclasses #13

Closed
Blaisorblade opened this issue Sep 4, 2014 · 8 comments
Closed

Covariant "lookup" for contravariant typeclasses #13

Blaisorblade opened this issue Sep 4, 2014 · 8 comments
Milestone

Comments

@Blaisorblade
Copy link

@paulp's has often brought this up. From memory, he'd have wanted Ordering[X] to be preferred to Ordering[Any] in implicit lookup, even though Ordering is contravariant, because Ordering[X] is still "more specific" for X. I remember that I agreed with @paulp and disagreed with Martin,

This seems bigger in scope than anything we've considered up to now, but we might want to consider it anyway.

@Blaisorblade
Copy link
Author

Again, see #14 for compatibility.

@puffnfresh puffnfresh added the bug label Sep 4, 2014
@non
Copy link

non commented Sep 4, 2014

If we determined that this was possible within our compatibility guidelines I'd support it 💯

@paulp
Copy link

paulp commented Sep 5, 2014

FYI the best rule I came up with is: implicit search first tries for an exact match, then follows the variance arrow the minimum possible distance. This rule would change how covariant types are selected as well, but it has the desirable quality of using the same rules for both directions.

aloiscochard pushed a commit to aloiscochard/scala that referenced this issue Sep 5, 2014
Move build boilerplate to standard.sbt.
@milessabin
Copy link
Member

What @non said.

@Blaisorblade
Copy link
Author

If we determined that this was possible within our compatibility guidelines I'd support it

This is certainly source-incompatible; given the result of #14, do we want -Zcontravariant-lookup (or similar), maybe as part of the default -Z flag?

@paulp
Copy link

paulp commented Sep 12, 2014

I found the ticket. https://issues.scala-lang.org/browse/SI-7768

@paulp
Copy link

paulp commented Sep 15, 2014

Here's some code I found lying around which accomplished nothing.

trait Ord[-T]

class OverloadSelection {
  // The return types vary to overcome "same type after erasure".
  def f(x: Ord[Iterable[Int]]): Int    = 1
  def f(x: Ord[     Seq[Int]]): Byte   = 2
  def f(x: Ord[    List[Int]]): Short  = 3

  println("Static overloading selection: " + List(
    f(new Ord[Iterable[Int]] { }),
    f(new Ord[     Seq[Int]] { }),
    f(new Ord[    List[Int]] { })
  ).mkString("  "))
}

class ImplicitSelection {
  implicit val Ord1: Ord[Iterable[Int]] = new Ord[Iterable[Int]] { override def toString = "1" }
  implicit val Ord2: Ord[     Seq[Int]] = new Ord[     Seq[Int]] { override def toString = "2" }
  implicit val Ord3: Ord[    List[Int]] = new Ord[    List[Int]] { override def toString = "3" }

  println("    Implicit value selection: " + List(
    implicitly[Ord[Iterable[Int]]],
    implicitly[Ord[     Seq[Int]]],
    implicitly[Ord[    List[Int]]]
  ).mkString("  "))
}

object Test {
  def main(args: Array[String]): Unit = {
    println("""
      |If there are several eligible arguments which match the
      |implicit parameter’s type, a most specific one will be
      |chosen using the rules of static overloading resolution.
      |  -- SLS 7.2, "Implicit Parameters"
      |""".stripMargin
    )
    new OverloadSelection
    new ImplicitSelection
  }
}

Here's a nice modernized example of how it burns to treat "Any" as the MOST SPECIFIC TYPE!

import java.util.function.Function;

public class C<A> {
    public <B> C<B> map (Function<? super A,B> f) { return null; }
    public <B> C<B> mapInv (Function<A,B> f) { return null; }
}

/****

% scala -Xexperimental -classpath .

scala> val c = new C[String]()
c: C[String] = C@14514713

scala> c mapInv (_.length)
res0: C[Int] = null

scala> c map (_.length)
<console>:9: error: missing parameter type for expanded function ((x$1) => x$1.length)
              c map (_.length)
                     ^
<console>:9: error: type mismatch;
 found   : java.util.function.Function[Any,Nothing] with Serializable
 required: java.util.function.Function[_ >: String, B]
Note: Nothing <: B (and java.util.function.Function[Any,Nothing] with Serializable <: java.util.function.Function[Any,Nothing]), but Java-defined trait Function is invariant in type R.
You may wish to investigate a wildcard type such as `_ <: B`. (SLS 3.2.10)
              c map (_.length)
                       ^

****/

@milessabin
Copy link
Member

To resurrect this issue, please rework it as an issue/PR against Lightbend Scala (ie. scala/scala).

@milessabin milessabin added this to the Parked milestone Aug 12, 2016
milessabin pushed a commit that referenced this issue Oct 24, 2016
Top level modules in Scala currently desugar as:

```
class C; object O extends C { toString }
```

```
public final class O$ extends C {
  public static final O$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class O$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  private O$();
    Code:
       0: aload_0
       1: invokespecial #13                 // Method C."<init>":()V
       4: aload_0
       5: putstatic     #15                 // Field MODULE$:LO$;
       8: aload_0
       9: invokevirtual #21                 // Method java/lang/Object.toString:()Ljava/lang/String;
      12: pop
      13: return
}
```

The static initalizer `<clinit>` calls the constructor `<init>`, which
invokes superclass constructor, assigns `MODULE$= this`, and then runs
the remainder of the object's constructor (`toString` in the example
above.)

It turns out that this relies on a bug in the JVM's verifier: assignment to a
static final must occur lexically within the <clinit>, not from within `<init>`
(even if the latter is happens to be called by the former).

I'd like to move the assignment to <clinit> but that would
change behaviour of "benign" cyclic references between modules.

Example:

```
package p1; class CC { def foo = O.bar}; object O {new CC().foo; def bar = println(1)};

// Exiting paste mode, now interpreting.

scala> p1.O
1
```

This relies on the way that we assign MODULE$ field after the super class constructors
are finished, but before the rest of the module constructor is called.

Instead, this commit removes the ACC_FINAL bit from the field. It actually wasn't
behaving as final at all, precisely the issue that the stricter verifier
now alerts us to.

```
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package p1; object O

// Exiting paste mode, now interpreting.

scala> val O1 = p1.O
O1: p1.O.type = p1.O$@ee7d9f1

scala> scala.reflect.ensureAccessible(p1.O.getClass.getDeclaredConstructor()).newInstance()
res0: p1.O.type = p1.O$@64cee07

scala> O1 eq p1.O
res1: Boolean = false
```

We will still achieve safe publication of the assignment to other threads
by virtue of the fact that `<clinit>` is executed within the scope of
an initlization lock, as specified by:

  https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5

Fixes scala/scala-dev#SD-194
milessabin pushed a commit that referenced this issue Jul 31, 2017
Non local returns aren't eliminated after inlined in 2.11 or 2.12

```
⚡ scala
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_112).
Type in expressions for evaluation. Or try :help.

scala> @inlune def foo(a: => Any) = if ("".isEmpty) a else ""
<console>:11: error: not found: type inlune
       @inlune def foo(a: => Any) = if ("".isEmpty) a else ""
        ^

scala> @inline def foo(a: => Any) = if ("".isEmpty) a else ""
foo: (a: => Any)Any

scala> class InlineReturn { def test: Any = foo(return "") }
defined class InlineReturn

scala> :javap -c InlineReturn#test
  public java.lang.Object test();
    Code:
       0: new           #4                  // class java/lang/Object
       3: dup
       4: invokespecial #32                 // Method java/lang/Object."<init>":()V
       7: astore_1
       8: getstatic     #36                 // Field $line4/$read$$iw$$iw$.MODULE$:L$line4/$read$$iw$$iw$;
      11: aload_1
      12: invokedynamic #59,  0             // InvokeDynamic #0:apply:(Ljava/lang/Object;)Lscala/Function0;
      17: invokevirtual #63                 // Method $line4/$read$$iw$$iw$.foo:(Lscala/Function0;)Ljava/lang/Object;
      20: goto          44
      23: astore_2
      24: aload_2
      25: invokevirtual #66                 // Method scala/runtime/NonLocalReturnControl.key:()Ljava/lang/Object;
      28: aload_1
      29: if_acmpne     39
      32: aload_2
      33: invokevirtual #69                 // Method scala/runtime/NonLocalReturnControl.value:()Ljava/lang/Object;
      36: goto          41
      39: aload_2
      40: athrow
      41: goto          44
      44: areturn
    Exception table:
       from    to  target type
           8    20    23   Class scala/runtime/NonLocalReturnControl
```

```
⚡ ~/scala/2.11.8/bin/scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_112).
Type in expressions for evaluation. Or try :help.

scala> @inline def foo(a: => Any) = if ("".isEmpty) a else ""
foo: (a: => Any)Any

scala> class InlineReturn { def test: Any = foo(return "") }
defined class InlineReturn

scala> :javap -c InlineReturn#test
  public java.lang.Object test();
    Code:
       0: new           #4                  // class java/lang/Object
       3: dup
       4: invokespecial #13                 // Method java/lang/Object."<init>":()V
       7: astore_1
       8: getstatic     #19                 // Field .MODULE$:L;
      11: new           #21                 // class InlineReturn$$anonfun$test$1
      14: dup
      15: aload_0
      16: aload_1
      17: invokespecial #24                 // Method InlineReturn$$anonfun$test$1."<init>":(LInlineReturn;Ljava/lang/Object;)V
      20: invokevirtual #28                 // Method .foo:(Lscala/Function0;)Ljava/lang/Object;
      23: goto          39
      26: astore_2
      27: aload_2
      28: invokevirtual #31                 // Method scala/runtime/NonLocalReturnControl.key:()Ljava/lang/Object;
      31: aload_1
      32: if_acmpne     40
      35: aload_2
      36: invokevirtual #34                 // Method scala/runtime/NonLocalReturnControl.value:()Ljava/lang/Object;
      39: areturn
      40: aload_2
      41: athrow
    Exception table:
       from    to  target type
           8    26    26   Class scala/runtime/NonLocalReturnControl

scala> :quit
```
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants