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

时间:2016-02-13 01:56:15

标签: scala generics functional-programming

摘要:我想为参数化类型的实例添加实例方法,但仅针对type参数的某些值。具体来说,我有List[E],但我只希望List[List[_]]的实例拥有flatten()方法。

我正在学习Scala和函数式编程的基础知识,接下来是Chiusano& amp;的 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的类型系统来表达此限制?

1 个答案:

答案 0 :(得分:1)

我们可以逐件建立我们需要的东西。首先我们知道我们需要<f9>的类型参数,因为我们没有办法引用内部元素类型:

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.flatten。我们的List[List[I]]允许我们将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 已损坏,但应该在您修复它时。