摘要:我想将实例方法添加到参数化的实例中,但仅适用于类型参数的某些值。具体来说,我有List[E]
,但我只希望List[List[_]]
的实例具有flatten()
方法。
我正在学习Scala和函数式编程的基础知识,方法是遵循Chiusano和Bjarnason在Scala中的函数式编程练习。
假设我有一个类型 List[E]
和一个伴随对象List
,它具有处理 List[E]
实例的方法。
sealed trait List[+E]
case object Nil extends List[Nothing]
case class Cons[+E](head: E, tail: List[E]) extends List[E]
object List {
def flatten[E](aListOfLists: List[List[E]]): List[E] = Nil
def foldLeft[E, F](aList: List[E])(acc: F)(f: (F, E) ⇒ F): F = acc
}
现在假设我想在 List
实例上创建类似的方法,这些方法只是将调用转发到伴随对象。我会尝试按如下方式补充特征定义。
sealed trait List[+E] {
def foldLeft[F](acc: F)(f: (F, E) => F) = List.foldLeft(this)(acc)(f)
}
我遇到了一个复杂问题:List.foldLeft()
适用于任何List[E]
,但List.flatten()
期望一个List[List[E]]
论点。因此,我只希望List[List[_]]
实例具有此方法。如何将flatten()
添加到List
实例的相应子集?如何使用 Scala 的类型系统来表达此限制?
我们可以一点一点地建立我们需要的东西。首先我们知道我们需要一个 flatten
的类型参数,因为我们没有办法引用内部元素类型:
sealed trait List[+E] {
def flatten[I] // ???
}
接下来,我们需要某种方法来确定我们的E
是List[I]
。我们不能向E
本身添加约束,因为在许多情况下,它不会List[I]
任何I
,但是如果我们希望能够调用flatten
,我们可以要求隐式证据证明这种关系必须成立:
sealed trait List[+E] {
def flatten[I](implicit ev: E <:< List[I]) = ???
}
请注意,出于与方差(和类型推断(相关的原因,我们需要使用 <:<
而不是 =:=
。
接下来我们可以添加返回类型,我们知道它必须是List[I]
:
sealed trait List[+E] {
def flatten[I](implicit ev: E <:< List[I]): List[I] = ???
}
现在我们希望能够在List[List[I]]
上呼叫List.flatten
。我们的ev
允许我们将 E
类型的值转换为 List[I]
,但我们没有E
值,我们只有一个List[E]
.有很多方法可以解决这个问题,但我将继续定义一个map
方法并使用它:
sealed trait List[+E] {
def map[B](f: E => B): List[B] = this match {
case Nil => Nil
case Cons(h, t) => Cons(f(h), t.map(f))
}
def flatten[I](implicit ev: E <:< List[I]): List[I] = List.flatten(map(ev))
}
然后:
val l1 = Cons(1, Cons(2, Nil))
val l2 = Cons(3, Cons(4, Cons(5, Nil)))
val nested = Cons(l1, Cons(l2, Nil))
val flattened: List[Int] = nested.flatten
这实际上不起作用,因为您的List.flatten
已损坏,但是当您修复它时应该这样做。