Mike
Mike

Reputation: 245

What are the rules for when trait/class type parameters take precedence vs method type parameters

I've been using scala for a while now and I thought I was really starting to understand everything (well, most things...), but I found myself confused by a number of the method definitions in the Map class. I know how foldLeft, etc work, but what I'm confused about is the type parameters used in the Map functions. Let's use foldLeft as an example:

foldLeft [B] (z: B)(op: (B, (A, B)) ⇒ B) : B

The definition for the Map trait itself takes two type parameters 'A' and 'B' (e.g. Map[A,+B]). From what I can tell, when you define a type parameter for a method using the same name as one of the type parameters for the class/trait, it overrides the class/trait value. Take this definition of Foo as an example:

class Foo[A : Manifest, B : Manifest] {
  def isAString() = manifest[A] == manifest[String]
  def isAInt() = manifest[A] == manifest[Int]
  def isBString() = manifest[B] == manifest[String]
  def isBInt() = manifest[B] == manifest[Int]
  def nowIsBString[B : Manifest] = manifest[B] == manifest[String]
}

scala> val f = new Foo[String,Int]
f: Foo[String,Int] = Foo@7bacb41

scala> f.isAString
res290: Boolean = true

scala> f.isAInt
res291: Boolean = false

scala> f.isBString
res292: Boolean = false

scala> f.isBInt
res293: Boolean = true

scala> f.nowIsBString[String]
res294: Boolean = true

scala> f.nowIsBString[Int]
res295: Boolean = false

So in the foldLeft definition, 'B' comes from the method definition and 'A' comes from the trait definition. For example:

val xm = Map("test" -> 1, "test2" -> 2)

scala> val foldFn = (z: Int, kv: (String, Int)) => z + kv._2 
foldFn: (Int, (String, Int)) => Int = <function2>

scala> m.foldLeft(0)(foldFn)
res298: Int = 3

This is as expected as 'B' for the function matches 'B' for the trait, but what if I change the 'B' type for the function to String instead of Int:

scala> val foldFn = (z: String, kv: (String, String)) => z + kv._2 
foldFn: (String, (String, String)) => java.lang.String = <function2>

scala> m.foldLeft("")(foldFn)
<console>:19: error: type mismatch;
 found   : (String, (String, String)) => java.lang.String
 required: (java.lang.String, (java.lang.String, Int)) => java.lang.String
              m.foldLeft("")(foldFn)

So let's change the kv parameter to (String, Int):

scala> val foldFn = (z: String, kv: (String, Int)) => z + kv._2 
foldFn: (String, (String, Int)) => java.lang.String = <function2>

scala> m.foldLeft("")(foldFn)
res299: java.lang.String = 12

Unlike my Foo example, in this case the Map's 'B' value is taking precedence over the functions definition, but only for the kv parameter. What I would have expected is to see a foldLeft defined as follows:

foldLeft[C] (z: C)(op: (C, (A, B)) => C): C

That would be more clear to me, but it is not defined this way. So does anyone know the rules for when a methods parameter will override a trait/class parameter and when it will not?

Upvotes: 2

Views: 1734

Answers (1)

Travis Brown
Travis Brown

Reputation: 139028

Scala is the same as Java in this respect, and the following from the "Names" chapter of the Java specification applies:

A declaration d of a type named n shadows the declarations of any other types named n that are in scope at the point where d occurs throughout the scope of d.

So the type parameter for a method will always shadow a class or trait type parameter with the same name. Your Foo example demonstrates this fact.

The apparent counterexample you're seeing in the case of Map's foldLeft is just an unpleasant artifact of the current version of Scaladoc, as is pointed out in the answers to the question you've linked. foldLeft isn't defined in the Map trait, but in TraversableOnce, where there isn't a trait type parameter named B at all.

In general shadowing the type parameter of a trait or class in a method is a really bad idea, of course.

Upvotes: 1

Related Questions