如何强制类型不变?

时间:2014-02-05 12:20:43

标签: scala scala-collections

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 SeqSet和{的通用解决方案{1}},甚至任何采用类型参数的Trait。如果这是不可能的,唯一的选择是放弃Scala集合,例如scalazpsp-view,那么这就是答案。

1 个答案:

答案 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