lower bound + contravariant type = unnecessary?

93 views
Skip to first unread message

Simon Schäfer

unread,
Apr 15, 2013, 8:02:19 PM4/15/13
to scala-l...@googlegroups.com
The following example compiles successfully:

case class ~[-A, B]
class X[+A, +B] {
def x[B1 >: B](implicit ev: A ~ B1) = ???
}

as well does this:

case class ~[A, B]
class X[+A, +B] {
def x[A1 >: A, B1 >: B](implicit ev: A1 ~ B1) = ???
}

The second example compiles as well when A in ~ is contravariant:

case class ~[-A, B]
class X[+A, +B] {
def x[A1 >: A, B1 >: B](implicit ev: A1 ~ B1) = ???
}

In the last example, does it matter if A1 exists? I think it is
unnecessary because for method x the lower bound A1 >: A and the type
declaration -A mean the same.

Paul Phillips

unread,
Apr 16, 2013, 2:25:34 PM4/16/13
to scala-l...@googlegroups.com
On Mon, Apr 15, 2013 at 5:02 PM, Simon Schäfer <ma...@antoras.de> wrote:
In the last example, does it matter if A1 exists? I think it is unnecessary because for method x the lower bound A1 >: A and the type declaration -A mean the same.

You can observe where it matters by trying to compile it, because if it matters, it won't compile. It'll say something like

./a.scala:14: error: covariant type B occurs in invariant position in type p2.~[A1,B] of value ev
    def x[A1 >: A](implicit ev: A1 ~ B) = ???
                            ^
one error found

where it matters, and nothing where it doesn't.

Simon Schäfer

unread,
Apr 16, 2013, 5:29:49 PM4/16/13
to scala-l...@googlegroups.com
Ok, that is true, but I thought that it could make a difference when x is called. There I can't directly know if there is a difference because I may probably come not up with all possible cases of type parameters on X and x. Because I didn't come up with any examples where it makes a difference I assume that there isn't one.

Then, there is an unnecessary type A1 in Either.foldLeft:

  def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1])

And for joinRight it is B1.

Paul Phillips

unread,
Apr 16, 2013, 6:10:07 PM4/16/13
to scala-l...@googlegroups.com

On Tue, Apr 16, 2013 at 2:29 PM, Simon Schäfer <ma...@antoras.de> wrote:
  def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1])

Yes, you can easily see either A1 or B1 has to be unnecessary, given that A and B have the same variance, but A1 and B1 are in opposite variance positions from one another. Sounds like a nice easy pull request!

Simon Schäfer

unread,
Apr 16, 2013, 7:07:03 PM4/16/13
to scala-l...@googlegroups.com
Done: https://github.com/scala/scala/pull/2401


--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Paul Phillips

unread,
Apr 16, 2013, 9:31:09 PM4/16/13
to scala-l...@googlegroups.com
The only tricky issue is that such a change is not source compatible, because someone might have explicitly applied type arguments. I'm not even sure there is presently a way to let both versions work simultaneously. 

Som Snytt

unread,
Apr 17, 2013, 1:11:48 AM4/17/13
to scala-l...@googlegroups.com
Yep, they pull you in with, "Sounds like a nice easy pull request!"

Once they have you hooked -- that's when the rebasing begins!

Jason Zaugg

unread,
Apr 17, 2013, 2:23:56 AM4/17/13
to scala-l...@googlegroups.com
On Wed, Apr 17, 2013 at 3:31 AM, Paul Phillips <pa...@improving.org> wrote:
The only tricky issue is that such a change is not source compatible, because someone might have explicitly applied type arguments. I'm not even sure there is presently a way to let both versions work simultaneously. 

I tried to make a deprecated macro with the old signature. Two problems.

First, it hits a "double definition: same type after erasure" error. Which is bogus, we don't generate a methods a macro def.

Disabling that error when a macro is involved:

scala> object Foo { def bar: Int = 0; def bar[A]: Int = macro barImpl[A]; def barImpl[A](c: Context): c.Expr[Int] = {c.warning(c.enclosingPosition, "deprecated"); c.universe.reify { 0 } } }
defined object Foo

scala> Foo.bar[Int]
<console>:13: warning: deprecated
              Foo.bar[Int]
                     ^

So far so good. (Okay, it would be nice to have a way to issue a bona fide deprecation warning from a macro.)

But:

scala> Foo.bar
<console>:13: error: ambiguous reference to overloaded definition,
both macro method bar in object Foo of type [A]=> Int
and  method bar in object Foo of type => Int
match expected type ?
              Foo.bar
                  ^

We need to convince overload resolution to pick the no-type param version. Seems hopeless, right? Not so!

scala> object Foo { def bar: Int = 0; def bar[A]: Any = macro barImpl[A]; def barImpl[A](c: Context): c.Expr[Any] = {c.warning(c.enclosingPosition, "deprecated"); c.universe.reify { 0 } } }
defined object Foo

scala> Foo.bar
res7: Int = 0

scala> Foo.bar[Int]
<console>:13: warning: deprecated
              Foo.bar[Int]
                     ^

By widening the declared type of the macro to Any, we can de-prioritize it in the static overload resolution. But the macro is free to expand to the more specific type.

Anyway, this seems a little fragile, so we would have to be really sure about it before we went down this path. But its always fun to discover new ways to employ macros :)

-jason
Reply all
Reply to author
Forward
0 new messages