TL;DR: It appears that type parameters of type aliases (e.g. type T[X<:Serializable]
) do not enforce their constraints when referenced as variables, parameters and perhaps other cases. Case classes, however, do enforce the bounds correctly for their parameters.
Consider a type alias designed to represent a subset of generic type. For example, let us say I want a type for lists of Serializable
things:
scala> type SerializableList[T <: Serializable] = List[T]
defined type alias SerializableList
Now say that I want a case class with a parameter of these things:
scala> case class NetworkDataCC(things: SerializableList[_])
<console>:9: error: type arguments [_$1] do not conform to type SerializableList's type parameter bounds [T <: Serializable]
case class NetworkDataCC(things: SerializableList[_])
Well, that doesn't work. Scala (annoyingly) does not carry the parameter bounds with the type, but it's easy to fix:
scala> case class NetworkDataCC(things: SerializableList[_ <: Serializable])
defined class NetworkDataCC
Alright. Looks good. Now, what if I want just a regular class with those things, but I again forget to explicitly declare the type bounds. I expect an error:
scala> class NetworkData(val things: SerializableList[_])
defined class NetworkData
Oh, wait. No error... huh.
So, now I can do this?
scala> new NetworkData(List(1))
res3: NetworkData = NetworkData@e344ad3
Well, that seems quite broken. The case class, works fine of course (because the restrictions were declared):
scala> NetworkDataCC(List(1))
<console>:11: error: type mismatch;
found : Int(1)
required: Serializable
NetworkDataCC(List(1))
In my project, I am making use of reflection to generate some metadata about my classes. The metadata for the non-case-class shows a lack of bounds on things
:
scala> classOf[NetworkData].getDeclaredFields()(0).getGenericType
res0: java.lang.reflect.Type = scala.collection.immutable.List<?>
Whereas the case class is correct:
scala> classOf[NetworkDataCC].getDeclaredFields()(0).getGenericType
res1: java.lang.reflect.Type = scala.collection.immutable.List<? extends scala.Serializable>
I wasn't able to find any bugs in the scala compiler bug tracker for this. Am I misunderstanding how these bounds should be used?
Scala's underscore is not equivalent to SerializableList[X forSome {type X}]
by default:
scala> def aa(a: SerializableList[_]) = a
aa: (a: SerializableList[_])List[Any]
scala> def aa(a: SerializableList[X forSome {type X}]) = a
<console>:11: error: type arguments [X forSome { type X }] do not conform to type SerializableList's type parameter bounds [T <: Serializable]
def aa(a: SerializableList[X forSome {type X}]) = a
^
scala> class NetworkDataCC(things: SerializableList[X forSome {type X}])
<console>:11: error: type arguments [X forSome { type X }] do not conform to typ
e SerializableList's type parameter bounds [T <: Serializable]
class NetworkDataCC(things: SerializableList[X forSome {type X}])
It is equivalent to
scala> def aa(a: SerializableList[X] forSome {type X} ) = a
aa: (a: SerializableList[_])List[Any]
So such "unbounded" behavior is fine. Check out this answer: https://stackoverflow.com/a/15204140/1809978
Case classes seem to have additional type restrictions (due to this bug, which affects unapply
method, automatically generated for case class).
If you want to have "unbounded" existential type in case class, just specify higher-order type explicitly:
scala> case class NetworkDataCC[SerializableList[_]](things: SerializableList[_])
warning: there were 2 feature warning(s); re-run with -feature for details
defined class NetworkDataCC
scala> NetworkDataCC(List(1))
res5: NetworkDataCC[List] = NetworkDataCC(List(1))
Or even:
scala> type SLL = SerializableList[_]
defined type alias SLL
scala> case class NetworkDataDD(things: SLL)
defined class NetworkDataDD
So this is definitely a bug regarding that you can workaround it with semantically equivalent type alias (see SI-8997)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With