Implicit resolution

13 views
Skip to first unread message

Lyle Kopnicky

unread,
Mar 17, 2015, 9:18:21 PM3/17/15
to pdxs...@googlegroups.com
Here's another puzzler:

I've created a type class, and made several instances. But the instances are not found when I use a subtype of one of the parameters in the instance.


The Scala spec here


says "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."

It seems to me this fits those rules.

Perhaps we could discuss this at the meeting tonight?

- Lyle

Robert Norris

unread,
Mar 17, 2015, 9:36:01 PM3/17/15
to pdxs...@googlegroups.com

Because A and B are used in contravariant position you need to declare it as Collidable[-A, -B] ... which can lead to surprising results sometimes because the arrows all flip around and "most specific" doesn't necessarily mean what you would intuitively think. But it seems to work ok here.

--
You received this message because you are subscribed to the Google Groups "pdxscala" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pdxscala+u...@googlegroups.com.
To post to this group, send email to pdxs...@googlegroups.com.
Visit this group at http://groups.google.com/group/pdxscala.
For more options, visit https://groups.google.com/d/optout.

Lyle Kopnicky

unread,
Mar 18, 2015, 12:23:52 AM3/18/15
to pdxs...@googlegroups.com
Ah, of course! it seems so obvious now that you point it out. Thanks, Rob! We also figured this out in parallel during the meeting.

What's funny is that I was writing this code as an experiment while preparing my talk about variance. And yet I forgot to apply variance. :)

Lyle Kopnicky

unread,
Mar 19, 2015, 11:22:36 PM3/19/15
to pdxs...@googlegroups.com

Although I understood why parameters A and B had to be contravariant (or invariant) in the Collidable trait (because they are in a contravariant position), I was still a little confused about how the implicit resolution worked.

Now I think it all makes sense to me.

trait Collidable[-A, -B] {
    def collideWith(a: A, b: B): String
}

This trait is function-like in that it’s basically a placeholder for this collideWith function. Intuitively, if something requires a particular instance of this trait (I mean, with the parameters filled in - is instance the right term?), with concrete types for A and B, then you could pass in something of that type, or you could pass in something where collideWith could handle a broader range of values - that is, with supertypes of A and B. That’s what contravariance is.

implicit object CollidableAsteroidWithSpaceship extends Collidable[Asteroid, Spaceship] {
    def collideWith(a: Asteroid, b: Spaceship) =
        "what happens when an asteroid collides with a spaceship"
}

This implicit object has type Collidable[Asteroid, Spaceship] - or does it? Actually it extends Collidable[Asteroid, Spaceship], so it seems to be a subtype of it. That part is still throwing me off.

def collideWith[A, B](a: A, b: B)(implicit ev: Collidable[A, B]): String =
    ev.collideWith(a, b)

This is the actual function we will call. Not to be confused with the collideWith method in the trait, to which it will delegate. It takes an implicit parameter of type Collidable[A,B]. Still a bit abstract, but hang on. Let’s look at the concrete invocation:

class BlueAsteroid extends Asteroid

println(collideWith(new BlueAsteroid, s1))

What collideWith are we calling? The def above. By calling it with concrete parameters, we’re instantiating its type parameters at A = BlueAsteroid, B = Spaceship. That means the implicit ev must have type Collidable[BlueAsteroid, Spaceship]. But, there is no implicit object with that type, so we’re stuck, right?

No. Remember that where a type is expected for an argument, you can always pass a subtype. So, the implicit resolver will also look for subtypes of Collidable[BlueAsteroid, Spaceship]. And it turns out that, due to the contravariance annotation, Collidable[Asteroid, Spaceship] is a subtype of Collidable[BlueAsteroid, Spaceship]. So the implicit object Collidable[Asteroid, Spaceship] will be used, exactly as we intended.

So, where it seemed like the contravariance was unfortunately backwards, it actually turned out to be in the right direction to solve our problem.

It seems like one would have worse problems if one tried to use a trait with parameters in covariant position - you’d just have to make them invariant.

- Lyle

Lyle Kopnicky

unread,
Mar 19, 2015, 11:24:40 PM3/19/15
to pdxs...@googlegroups.com
Ah right - it's OK if the implicit object is a subtype of Collidable[Asteroid, Spaceship], because we can always use a subtype for the evidence parameter.
Reply all
Reply to author
Forward
0 new messages