根据参数值和函数参数类型推断出一个常见的超类型

时间:2010-06-28 10:38:46

标签: scala scala-2.8 type-inference

如果编译以下内容而不需要this上的显式类型定义吗?

def prepList[B >: A](prefix: PlayList[B]) : PlayList[B] =
  prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))

在我看来,这种类型应该能够推断出来。这仅仅是Scala编译器的一个限制,还是存在类型理论上无法做到这一点的原因?我还没有真正意识到Scala类型推理器可以处理的内容。

完成该方法:

    根据定义
  • B >: A
  • this的类型为PlayList[A],这是PlayList[B]的子类型,因为B >: A和PlayList在A中是协变的。
  • node的类型为B,参数类型为prefix
  • f中函数参数foldr的第二个参数与B的第一个参数具有相同的类型(声明为foldr)。
  • 因此suffixthis具有相同的类型,因此特别是PlayList[A]。由于B >: Asuffix.prepNode()需要B

我希望编译器看到suffix.prepNode(node)node类型为B的情况下是合法的。只有在foldr的调用或在该调​​用中对this的引用上显式指定类型时,它似乎才能执行此操作。

有趣的是,如果我将函数参数的显式类型指定为(node: B, suffix: PlayList[B]),则仍会在方法调用suffix.prepNode(node)的参数上生成类型不匹配错误:"found: B, required: A"

我正在使用Scala 2.8 RC6。下面的完整示例中,有问题的行是第8行。

sealed abstract class PlayList[+A] {
  import PlayList._
  def foldr[B](b: B)(f: (A, B) => B): B

  def prepNode[B >: A](b: B): PlayList[B] = nel(b, this)
  def prepList[B >: A](prefix: PlayList[B]): PlayList[B] =
    // need to specify type here explicitly
    prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))

  override def toString = foldr("")((node, string) => node + "::" + string)
}

object PlayList {
  def nil[A]: PlayList[A] = Nil
  def nel[A](head: A, tail: PlayList[A]): PlayList[A] = Nel(head, tail)
  def nel[A](as: A*): PlayList[A] = as.foldRight(nil[A])((a, l) => l.prepNode(a))
}

case object Nil extends PlayList[Nothing] {
  def foldr[B](b: B)(f: (Nothing, B) => B) = b
}
case class Nel[+A](head: A, tail: PlayList[A]) extends PlayList[A] {
  def foldr[B](b: B)(f: (A, B) => B) = f(head, tail.foldr(b)(f))
}

编辑:第二次尝试通过编译步骤进行推理

  • 为了清晰起见,重命名foldr采用类型(T)((U, T) => T)的参数。我们正在尝试推断类型UT的值。
  • 第一个参数foldr与函数的第二个参数之间存在关系 - 它们是相同的,T。 (部分回答丹尼尔。)
  • 我们传递的对象类型为this: PlayList[A]suffix: PlayList[B]
  • 因此,自B >: A以来,最具体的常见超类型是PlayList[B];因此我们有T == PlayList[B]注意我们不需要UT之间的任何关系来推断这一点。

这是我被困的地方:

  • 从编译错误消息中,推理器清楚地认为node具有类型B(即U == B)。
  • 我无法看到U == B如何在不suffix的类型参数中推断它的结论。 (scala编译器可以这样做吗?)
  • 如果推断的步骤发生了,那么它遵循U == B,我们已成功编译。那一步出错了?

编辑2:在重命名上面的foldr参数类型时,我错过了U == A的定义,它是PlayList类的类型参数。我认为这仍然与上述步骤一致,因为我们在PlayList[B]的实例上调用它。

因此,在呼叫网站上,T == PlayList[B]是最不常见的超级类型,而U == B根据接收者的foldr定义。这似乎足够简洁,可以缩小到几个选项:

  • 编译器无法解析这些多种类型并计算B
  • 的上限
  • PlayList[B]的返回类型foldr获取prepNode的参数类型(持怀疑态度)

2 个答案:

答案 0 :(得分:3)

我不是类型专家,但是当我试图推断时会发生这种情况。

((node, suffix) => suffix.prepNode(node))返回一些未知类型PlayList[T],其中T扩展为A.它作为参数传递给foldr,它返回传递给它的函数的类型(PlayList[T],其中T扩展A)。这应该是某种类型PlayList[B]

所以我的猜测是this:PlayList[B]是必要的,表明T和B是相关的。

您可能需要让PlayList在两种类型PlayList[+A, B >: A]中参数化,因为您的prepNode和propList似乎与扩展A的相同类型有效吗?

换句话说,您的原始类定义可以像这样定义:

def prepNode[T >: A](b: T): PlayList[T]
def prepList[U >: A](prefix: PlayList[U]): PlayList[U]

但是你在两种情况下都使用了B,并且编译器不知道T和U是相同的。


编辑,您可以使用-explaintypes选项,并根据您获得的类型提示查看编译器的功能。这是explaintypes的输出并删除:PlayList[B](使用2.8.0.RC1):

$ scalac -explaintypes -d classes Infer.scala
found   : node.type (with underlying type B)
 required: A
    prefix.foldr(this)((node, suffix) => suffix.prepNode(node))
                                                         ^
node.type <: A?
  node.type <: Nothing?
    B <: Nothing?
      <notype> <: Nothing?
      false
      Any <: Nothing?
        <notype> <: Nothing?
        false
      false
    false
  false
  B <: A?
    B <: Nothing?
      <notype> <: Nothing?
      false
      Any <: Nothing?
        <notype> <: Nothing?
        false
      false
    false
    Any <: A?
      Any <: Nothing?
        <notype> <: Nothing?
        false
      false
    false
  false
false

希望这有助于解决问题。可能是一个关于scalac可以推断出来的问题以及什么时候无法推断它会有所帮助。

答案 1 :(得分:1)

问题是foldr没有指定B >: A,因此,就foldr而言,它自己的A和{{1}之间没有任何关系类型。就B而言,foldrsuffix完全无关 - 即使您碰巧已将相关参数传递给它。