user2018791
user2018791

Reputation: 1153

confusion in understanding scala's bound

I am reading Demystifying Scala Type System , in the 17th slide there is a snippet:

class Test[+A] {
  def test[B >: A](b: B): String = b.toString
}

The slide says method test will accept type A or any super type of A. But seems I could pass any type to test.

vat t = new Test[Int]
t.test("foo")
t.test(List(1, 2, 3))

I have the same confusion when I read Programming in Scala .

Upvotes: 4

Views: 579

Answers (2)

Xiaohe Dong
Xiaohe Dong

Reputation: 5023

The parameter can accept any type because of Any. However, I dont think this example is very clear. If you put this snippet into worksheet

if defined the return type is something related to B, you will see the problems

class Test[+A] {
  def test[B >: A](b: B): B = b
}


class A

class B extends A

val test = new Test[B]

val t = test.test("test")

The return type is not String, is Object. so basically, it loses the type reference.

The reason we need to define like this is A is covariant. In this case, it can only be used in return type not parameter because of the definition of Function1[-A, +B]. If use def test(b: A)..... it will have compilation errors:

covariant type A occurs in contravariant position in type A of value b def test(b: A): String = b.toString

Further, if you want to constrain the super type, you can have several options. one of it is using implicit to restrain the type.

class C

class CCC extends C

def test[B >: A](b: B)(implicit ev: B =:= C): B = b

val test = new Test[CCC]

test.test(new C)//ok

test.test("123")//compilation error

Upvotes: 0

godfatherofpolka
godfatherofpolka

Reputation: 1683

The important thing to remember here is that Any is a super type of any type, i.e.

Any >: A

In particular, assume

val t = new Test[Int]

This is, A is Int. Now we call

t.test("foo")

"foo" is of type String, but string is subtype of Any, and hence can be considered as such, therefore test[B >: A](b : B) can be called with b being "foo" and B being Any.

The following example should illustrate this, consider

class Test[+A](a : A) {
  def test[B >: A](b : B) : (A,B) = (a,b)
}

now, using

val t = new Test(3)
val x = t.test("foo")

we get

x: (Int, Any) = (3,foo)

Finally, to add some details, Scala will not always pick Any, but the least common supertype of A and B. This happens to be Any for Int and String (see http://www.scala-lang.org/old/node/128 ), but may be something different for other examples, e.g., for

val s = new Test(Nil)
val y = s.test("foo")
val z = s.test(List(1))

we will get

y: (scala.collection.immutable.Nil.type, java.io.Serializable) = (List(),foo)
z: (scala.collection.immutable.Nil.type, List[Int]) = (List(),List(1))

Also note that the lower bound does not prevent passing subtypes of A

scala> val a = new Test(new AnyRef())
a: Test[java.lang.Object] = Test@6771a12

scala> a.test("foo")
res6: (java.lang.Object, java.lang.Object) = (java.lang.Object@78b99f12,foo)

So, the question is, what are lower type bounds useful for? One possible answer is that they can be used for "controlling" the types of "output" positions, as typically used for covariant type parameters, see, e.g., http://docs.scala-lang.org/tutorials/tour/lower-type-bounds.html Roughly speaking, when appending an element to a (covariant) list of type A, you want to make sure that the resulting list is "at least" of type A. (I'm sorry for this being in parts hand-wavey, but as it goes beyond the scope of the original question, I just wanted to give a brief idea of why they are needed, for a full answer, it's probably better to create a new question)

Upvotes: 6

Related Questions