- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
X[String] is not a subtype of X[AnyRef]
Thu, 2009-05-07, 16:52
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)
}
Thu, 2009-05-07, 17:17
#2
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>
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.
Thu, 2009-05-07, 18:37
#3
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...
Thu, 2009-05-07, 18:57
#4
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
<!-- @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:
--
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