This page is no longer maintained — Please continue to the home page at www.scala-lang.org

X[String] is not a subtype of X[AnyRef]

4 replies
razie
Joined: 2008-11-07,
User offline. Last seen 2 years 4 weeks ago.

I don't quite understand it...but it looks that way. If you copy this code
into a file, you'll get a compiler error about expected types in the last
two lines...

class XBase [T] {
def b(x:T) : String = "base"
}

class MyX extends XBase[String] {
def b(x:String) = x
}

object TakesParms {
def a (x:XBase[AnyRef]) = 1
}

class XClient {
Bibi a (new XBase[String])
Bibi a (new MyX)
}

David Pollak
Joined: 2008-12-16,
User offline. Last seen 42 years 45 weeks ago.
Re: X[String] is not a subtype of X[AnyRef]
This relates to variance.  Here's the section from Beginning Scala on Variance:

<!-- @page { margin: 0.79in } P { text-indent: 0.25in; margin-bottom: 0in; color: #000; line-height: 0.18in; widows: 2; orphans: 2 } P.western { font-family: "Times New Roman", serif; font-size: 11pt; so-language: en-US } P.cjk { font-family: "Arial", sans-serif; font-size: 11pt } P.ctl { font-family: "Times New Roman", serif; font-size: 10pt; so-language: ar-SA } H1 { margin-top: 0.25in; margin-bottom: 0.08in } H1.western { font-family: "Arial", sans-serif; font-size: 18pt } H1.cjk { font-family: "DejaVu Sans"; font-size: 18pt } H1.ctl { font-family: "DejaVu Sans"; font-size: 18pt; font-weight: normal } P.-char-char3-western { margin-top: 0.08in; font-family: "Times New Roman", serif; font-size: 11pt; so-language: en-US } P.-char-char3-cjk { margin-top: 0.08in; font-family: "Arial", sans-serif; font-size: 11pt } P.-char-char3-ctl { margin-top: 0.08in; font-family: "Times New Roman", serif; font-size: 10pt; so-language: ar-SA } H2 { margin-top: 0.25in; margin-bottom: 0.08in } H2.western { font-family: "Nimbus Roman No9 L", serif; font-size: 14pt } H2.cjk { font-family: "DejaVu Sans"; font-size: 14pt } H2.ctl { font-family: "DejaVu Sans"; font-size: 14pt; font-weight: normal } -->

Variance

Variance is an important and challenging concept. It defines the rules by which parameterized types can be passed as parameters. In the beginning of the chapter, we showed how passing a String[] (Java notation) to a method expecting an Object[] can cause problems. Java allows you to pass an array of something to a method expecting an array of something’s superclass. This is called covariance. On the surface, this makes a lot of sense. If you can pass a String to a method expecting an Object, why can’t you pass an Array[String] (Scala notation) to a method expecting an Array[Object]? Because Array is mutable: it can be written to in addition to being read from, so a method that takes an Array[Object] may modify the Array by inserting something that cannot be inserted into an Array[String]. Defining the type variance for type parameters allows you to control how parameterized types can be passed to methods.

Variance comes in three flavors: invariant, covariant, and contravariant. Type parameters can be individually marked as covariant or contravariant and are by default invariant.

Invariant Parameter Types

In Scala, Array[T] is invariant. This means that you can only pass an Array[String] to foo(a: Array[String]) and that you can only pass an Array[Object] to bar(a: Array[Object]). This ensures that what is read from or written to the array is something of the correct type. So, for anything that’s mutable, the type parameter should be invariant. You do this by doing nothing with the type parameter. So, let’s define an invariant class:

class Holder[T](var data: T)

The class holds data of type T. Let’s write a method:

scala> def add(in: Holder[Int]) {in.data = in.data + 1}

add: (Holder[Int])Unit

scala> val h = new Holder(0)

h: Holder[Int] = Holder@bc0eba

scala> add(h)


scala> h.data

res2: Int = 1

Because the add method expects an Int to come out of Holder and puts an Int back into the Holder, the type of the Holder must be invariant. That does not mean that invariant containers lose their ability to hold subclasses of their declared type. A Holder[Number] can contain a Double, and an Array[Object] can contain String, Integer, and so on. Let’s put a Double into a Holder[Number]:

scala> val nh = new Holder[Number](33.3d)

nh: Holder[java.lang.Number] = Holder@340c9c

And we define a method that rounds the number:

scala> def round(in: Holder[Number]) {in.data = in.data.intValue}

round: (Holder[java.lang.Number])Unit

We call the round method, and let’s see what we get out the other side:

scala> round(nh)


scala> nh.data

res16: java.lang.Number = 33

We put in a Number and got back a Number. What’s the underlying class for the Number?

scala> nh.data.getClass

res17: java.lang.Class[_] = class java.lang.Integer

Great. Integer is a subclass of Number, so we can put a Integer or a Double into the Holder[Number]. We preserve the ability to use class hierarchies with invariant type parameters. Let’s finally see what happens when we try to pass a Holder[Double] into round.

scala> val dh = new Holder(33.3d)

dh: Holder[Double] = Holder@1801e5f

scala> round(dh)

<console>:8: error: type mismatch;

found : Holder[Double]

required: Holder[java.lang.Number]

So, invariant type parameters protect us when we have mutable data structures like arrays. Let’s move on to covariant parameter types.

Covariant Parameter Types

Covariant parameter types are designated with a + before the type parameter. A covariant type is useful for read-only containers. Scala’s List is defined as List[+T], which means that it’s covariant on type T. List is covariant because if you pass a List[String] to a method that expects a List[Any], then every element of the List satisfies the requirement that is an Any and we cannot change the contents of the List.

Let’s define an immutable class, Getable. Once an instance of Getable is created, it cannot change, so we can mark its type, T, as covariant.

scala> class Getable[+T](val data: T)

defined class Getable

Let’s define a method that takes a Getable[Any]:

scala> def get(in: Getable[Any]) {println("It's "+in.data)}

get: (Getable[Any])Unit

We define an instance of Getable[String]:

scala> val gs = new Getable("String")

gs: Getable[java.lang.String] = Getable@10a69f0

We can call get with gs:

scala> get(gs)

It's String

Let’s try the same example but passing a Getable[java.lang.Double] into something that expects a Getable[Number]:

scala> def getNum(in: Getable[Number]) = in.data.intValue

getNum: (Getable[java.lang.Number])Int

scala> def gd = new Getable(new java.lang.Double(33.3))

gd: Getable[java.lang.Double]

scala> getNum(gd)

res7: Int = 33

Yes, the covariance works the way we expect it to. We can make read-only classes covariant. I guess that means that contravariance is good for write-only classes.

Contravariant Parameter Types

So, if covariance allows us to pass List[String] to a method that expects List[Any], what good is contravariance? Let’s first look at a write-only class, Putable:

scala> class Putable[-T] {

def put(in: T) {println("Putting "+in)}

}

Next, let’s define a method that takes a Putable[String]:

scala> def writeOnly(in: Putable[String]) {in.put("Hello")}

writeOnly: (Putable[String])Unit

And let’s declare an instance of Putable[AnyRef]:

scala> val p = new Putable[AnyRef]

p: Putable[AnyRef] = Putable@75303f

And what happens if we try to call writeOnly?

scala> writeOnly(p)

Putting Hello

Okay, so we can call a method that expects a Putable[String] with a Putable[AnyRef] because we are guaranteed to call the put method with a String, which is a subclass of AnyRef. Standing along, this is not particularly valuable, but if we have a class that does something with input that results in output, the value of contravariance becomes obvious. The inputs to a transformation are contravariant. Calling something that expects at least any AnyRef with a String is legal and valid. But the return value can be covariant because we expect to get back a Number, so if we get an Integer, a subclass of Number, we’re okay. Let’s see how it works. We’ll define DS with a contravariant In type and a covariant Out type:

scala> trait DS[-In, +Out]{def apply(i: In): Out}

defined trait DS

Let’s create an instance that will convert Any into an Int:

scala> val t1 = new DS[Any, Int]{def apply(i: Any) = i.toString.toInt}

t1: java.lang.Object with DS[Any,Int] = $anon$1@14dcfad

We define check, a method that takes a DS[String, Any]:

scala> def check(in: DS[String, Any]) = in("333")

check: (DS[String,Any])Any

And we call check with t1:

scala> check(t1)

res14: Any = 333

Rules of Variance

So, we’ve successfully defined and used an invariant type. The invariant type was mutable, so it both returned and was called with a particular type. We created a convariant type which was an immutable holder of a value. Finally, we created a transformer that had contravariant input and covariant output. Wait, that sounds like a function. That’s right, Scala’s FunctionN traits have contravariant parameters and covariant results. This leads us to the simple rules of variance:

* Mutable containers should be invariant.

* Immutable containers should be covariant.

* Inputs to transformations should be contravariant, and outputs from transformations should be covariant.



On Thu, May 7, 2009 at 8:51 AM, Razie <razvanc99@yahoo.com> wrote:

I don't quite understand it...but it looks that way. If you copy this code
into a file, you'll get a compiler error about expected types in the last
two lines...

class XBase [T] {
 def b(x:T) : String = "base"
}

class MyX extends XBase[String] {
 def b(x:String) = x
}

object TakesParms {
 def a (x:XBase[AnyRef]) = 1
}

class XClient {
 Bibi a (new XBase[String])
 Bibi a (new MyX)
}

--
View this message in context: http://www.nabble.com/X-String--is-not-a-subtype-of-X-AnyRef--tp23428970p23428970.html
Sent from the Scala mailing list archive at Nabble.com.




--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp
Ricky Clarkson
Joined: 2008-12-19,
User offline. Last seen 3 years 2 weeks ago.
Re: X[String] is not a subtype of X[AnyRef]
A Cage[Cat] is not a Cage[Animal], if you can add new elements to it.

val animals: Cage[Animal] = new Cage[Cat]
animals += new Dog

youch.

If a Cage is immutable, you could use variance annotations to allow a Cage[Cat] to be a Cage[Animal].

And someone said OO was natural..

2009/5/7 Razie <razvanc99@yahoo.com>

I don't quite understand it...but it looks that way. If you copy this code
into a file, you'll get a compiler error about expected types in the last
two lines...

class XBase [T] {
 def b(x:T) : String = "base"
}

class MyX extends XBase[String] {
 def b(x:String) = x
}

object TakesParms {
 def a (x:XBase[AnyRef]) = 1
}

class XClient {
 Bibi a (new XBase[String])
 Bibi a (new MyX)
}

--
View this message in context: http://www.nabble.com/X-String--is-not-a-subtype-of-X-AnyRef--tp23428970p23428970.html
Sent from the Scala mailing list archive at Nabble.com.


razie
Joined: 2008-11-07,
User offline. Last seen 2 years 4 weeks ago.
Re: X[String] is not a subtype of X[AnyRef]

WHOA...missed that entire chapter...

Thank you...now i kind'a get it...

So this is the proper syntax:

class XBase [+T] {
def b[U>:T](x:U) : String = "base"
}

class MyX extends XBase[String] {
override def b[U>:String](x:U):String = x.asInstanceOf[String]
}

...

// it's annoying that I still have to do x.asInstanceOf[String] when i
clearly set String as the lower bounds for U...

milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: X[String] is not a subtype of X[AnyRef]

On Thu, May 7, 2009 at 5:15 PM, Ricky Clarkson wrote:
> And someone said OO was natural..

Who told you that nature was simple? ;-)

Cheers,

Miles

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland