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

Joe*_*ney 7 scala type-inference scala-2.8

是否应编译以下内容而不需要明确的类型定义this

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

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

通过该方法:

  • B >: A 根据定义
  • this具有类型PlayList[A],这是的一个子类型PlayList[B],因为B >: A和播放列表在协变A.
  • node有类型B,参数类型prefix.
  • 函数参数fin的第二个参数与第一个参数的foldr类型(声明B)相同foldr.
  • 因此suffix具有相同的类型this,因此特别是它PlayList[A].从那以后B >: A,suffix.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))
}
Run Code Online (Sandbox Code Playgroud)

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

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

这是我被卡住的地方:

  • 从编译错误消息中,推理器清楚地认为它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]的错误foldrprepNode

huy*_*hjl 3

我不是类型专家,但当我尝试推断时会发生以下情况。

((node, suffix) => suffix.prepNode(node))返回某种未知类型PlayList[T],其中 T 扩展 A 。它作为参数传递给foldr,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]
Run Code Online (Sandbox Code Playgroud)

但是您在两种情况下都使用了 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
Run Code Online (Sandbox Code Playgroud)

希望这有助于阐明一些道理。可能是关于 scalac 何时可以推断以及何时不能推断的问题会有所帮助。