将方法限制为调用方类型参数的值子集



摘要:我想将实例方法添加到参数化的实例中,但仅适用于类型参数的某些值。具体来说,我有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] // ???
}

接下来,我们需要某种方法来确定我们的EList[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已损坏,但是当您修复它时应该这样做。

最新更新