Reputation: 561
scala> case class Data[+T](val value:T=null)
defined class Data
scala> val foo=Data[ArrayBuffer[Data[Any]]]()
foo: Data[scala.collection.mutable.ArrayBuffer[Data[Any]]] = Data(null)
scala> foo.value+=Data[String]()
java.lang.NullPointerException
... 33 elided
I would like to have a Data class that is instantiated either as Data[String], Data[ArrayBuffer[Data[Any]]] or Data[Map[String,Data[Any]]]. In the above example I try to instantiate it as Data[ArrayBuffer[Data[Any]]] and add a Data[String] to its arraybuffer. Of course I get a null pointer exception because value is null. But the point of this example that it at least compiles and runs.
Now, in the Data constructor I would like to instantiate value as either a Data[String], an ArrayBuffer[Data[Any]] or Map[String,Data[Any]] depending on the type of the initially null value returned by the getClass method. However for this I need value to be a var, so that I can modify it after examining the type of its null value.
However I get this error:
scala> case class Data[+T](var value:T=null)
<console>:11: error: covariant type T occurs in contravariant position in type T of value value_=
case class Data[+T](var value:T=null)
Upvotes: 2
Views: 274
Reputation: 40500
Make your Data
invariant in T
. Just remove the +
: Data[T]
- this should compile.
Better yet, rethink your design to get rid of nulls and mutable variables - they both smell.
Edit: after reading your comments, I understand better what you are trying to do. Consider something like this for example as one of the options.
sealed trait Node
case class ListNode(list: Seq[Node]) extends Node
case class MapNode(map: Map[String, Node]) extends Node
case class LeafNode(data: String) extends Node
Now you can parse your document with something like (this is "pseudocode", adjust it to whatever xml-parsing library you are using):
def parseNode(tag: XMLTag): Node = tag.tagType match {
case LIST =>
val subNodes = tag.getNestedTags.map(parseNode)
ListNode(subNodes)
case MAP =>
val subNodes = tag.getNestedTags.map { tag =>
tag.name -> parseNode(tag)
}
MapNode(subNodes.toMap)
case _ =>
LeafNode(tag.text)
}
Upvotes: 4
Reputation: 561
I can make it work this way:
package data
case class Data[+T](val value:T)
{
println(value.getClass)
}
This way I have to explicitly initialize value from the constructor. Nothing wrong with this, just I find it a bit too verbose.
import data.Data
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map
object Run extends App
{
val a=Data[ArrayBuffer[Data[Any]]](ArrayBuffer[Data[Any]]())
a.value+=Data[String]("bar")
println(a.value)
val m=Data[Map[String,Data[Any]]](Map[String,Data[Any]]())
m.value+=("foo"->Data[String]("bar"))
println(m.value)
}
This prints:
class scala.collection.mutable.ArrayBuffer
class java.lang.String
ArrayBuffer(Data(bar))
class scala.collection.mutable.HashMap
class java.lang.String
Map(foo -> Data(bar))
The program only compiles with Data having +T type parameter, otherwise I get the error:
type mismatch;
[error] found : data.Data[String]
[error] required: data.Data[Any]
[error] Note: String <: Any, but class Data is invariant in type T.
[error] You may wish to define T as +T instead. (SLS 4.5)
[error] a.value+=Data[String]("bar")
Upvotes: 0
Reputation: 21690
http://like-a-boss.net/2012/09/17/variance-in-scala.html#variance_and_type_safety
Variance and type safety
When defining a generic class with a var field we can get compile time errors:
scala> class Invariant[T](var t: T)
defined class Invariant
scala> class Covariant[+T](var t: T)
<console>:7: error: covariant type T occurs in contravariant position in type T of value t_=
class Covariant[+T](var t: T)
^
scala> class Contravariant[-T](var t: T)
<console>:7: error: contravariant type T occurs in covariant position in type => T of method t
class Contravariant[-T](var t: T)
Let’s break it down a little. Why doesn’t the compiler allow getters in the Covariant class?
scala> abstract trait Covariant[+T] {
| def take(t: T): Unit
| }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
def take(t: T): Unit
^
scala> abstract trait Contravariant[-T] {
| def take(t: T): Unit
| }
defined trait Contravariant
Why? Let’s think about usages of covariance let’s say that we have a class:
class Printer[+T] {
| def print(t: T): Unit = ???
| }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
def print(t: T): Unit = ???
If the print method can print Dogs does it make sense (in general) that it should also print Animals? Maybe sometimes but in the general sense if we want to generalize the Printer class we should use contravariance. The compiler is smart enough to check this type of usage for us.
Let’s think about the second use case: returning a generic parameter:
scala> class Create[-T] {
| def create: T = ???
| }
<console>:8: error: contravariant type T occurs in covariant position in type => T of method create
def create: T = ???
And again - does it make sense that Create should generalize by contravariance? If Create returns instances of the Animal class should we be able to use it in every place that expects Create[Dog]? The scala compiler is smart enough that it explodes in our face if we try it.
Upvotes: 3