Scala中的列表是协变的(List[A+]
)。我发现这比其他任何东西都让我更麻烦,我正在寻找一种方法来强化我的列表中的类型不变性。以下应该给出编译错误:
scala> val l: List[Int] = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)
scala> "string" :: l
res0: List[Any] = List(string, 1, 2, 3)
scala> 1.0 :: l
res1: List[AnyVal] = List(1.0, 1, 2, 3)
编辑:请注意,这是一个简单的示例,我想知道是否有适用于所有scala Seq
,Set
和{的通用解决方案{1}},甚至任何采用类型参数的Trait。如果这是不可能的,唯一的选择是放弃Scala集合,例如scalaz或psp-view,那么这就是答案。
答案 0 :(得分:8)
您可以指定结果类型:
val rescala> val result: List[Int] = "string" :: l
<console>:8: error: type mismatch;
found : String
required: Int
val result: List[Int] = "string" :: l
^
你也可以像这样创建自己的不变方法:
def prepend[T1, T2](t: T1, l: List[T2])(implicit e: T1 =:= T2) = e(t) :: l
prepend(0, l)
// List[Int] = List(0, 1, 2, 3)
scala> prepend("str", l)
<console>:10: error: Cannot prove that String =:= Int.
prepend("str", l)
^
使用value classes,您可以为List
创建不变的包装器,而不会像这样运行时惩罚:
case class InvariantList[T](l: List[T]) extends AnyVal {
def ::(t: T) = InvariantList(t :: l)
}
val l = InvariantList(1 :: 2 :: 3 :: Nil)
0 :: l
// InvariantList(List(0, 1, 2, 3))
scala> "str" :: l
<console>:13: error: type mismatch;
found : String
required: Int
"str" :: l
^
您还可以使用scalaz
中的不变方法进行集合连接:
import scalaz._, Scalaz._
List(0) |+| List(1, 2, 3)
// List(0, 1, 2, 3)
Vector('a) |+| Vector('b, 'c)
// Vector('a, 'b, 'c)
scala> List("string") |+| List(1, 2, 3)
<console>:14: error: type mismatch;
found : Int(1)
required: String
List("string") |+| List(1, 2, 3)
^
请注意(如@drexin所述)scalaz中有一个不变的列表:IList。