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