Scala 中具有 F 界类型和存在类型的编译问题

Compilation issue in Scala with F-bounded types and existential types

我正在使用 F 有界类型以便能够return 当前类型

trait Board[T <: Board[T]] {
  def updated : T
}

我正在尝试编写一个使用它的通用辅助方法。

问题是:为什么以下不编译?

object BoardOps {
  def updated(board: Board[_]) = {
    board.updated.updated
  }
}

错误是value updated is not a member of _

我已经找到了这两个解决方法。它们是等价的吗?

object BoardOps {
  def updated(board: Board[_<:Board[_]]) = {
    board.updated.updated
  }
}

object BoardOps {
  def updated[T <: Board[T]](board: T) : T = {
    board.updated.updated
  }
}

why the following do not compile?

使用Board[_]作为参数类型告诉编译器"I do not care for the type of parameter inside board"。也就是说,对于编译器来说,this 是一种存在类型,它不知道有关该类型的任何细节。因此,board.updated return 是 "unspeakable" 或 opaque 类型,因为我们告诉编译器 "throw out" 该类型信息。

I've figured out these 2 workarounds. Are they equivalent?

您之前的示例使用了一个存在类型,并限制为 Board[_] 的子类型,或者更正式地说,我们这样写:

Board[T] forSome { type T <: Board[U] }

编译器实际命名 T -> $_1U -> $_2

的地方

同样,我们对内部类型参数一无所知,只知道它有一个上限 Board[_]。您的后一个示例使用名为 T 的通用量化类型,编译器可以使用该类型将方法的 return 类型推断为特定类型 T 而不是 Any.

回答你的问题,不,它们不等价。存在类型和普遍量化类型彼此 对偶 。有关存在主义的更多信息,请访问 What is an existential type? and https://www.drmaciver.com/2008/03/existential-types-in-scala/

它不会编译,因为一旦你写 Board[_],编译器就不会推断出任何关于匿名类型参数 _.

的有用信息

有几个work-arounds(和你已经提出的不一样):

  1. 使用Board[X] forSome { type X <: Board[X] }
  2. 使用模式匹配来推断有关类型的更多信息

使用forSome存在量化

这可以通过 forSome 存在量化轻松解决:

import scala.language.existentials

object BoardOps_forSome {
  def updated(board: Board[X] forSome { type X <: Board[X] }) = {
    board.updated.updated.updated
  }
}

这允许您调用 updated 无限次。


使用模式匹配

实际上,您可以使用模式匹配在不更改签名的情况下解决它。例如,这个不敬虔的构造允许您应用方法 updated 三次(无限次):

object BoardOps_patternMatch {
  def updated(board: Board[_]) = {
    board match {
      case b: Board[x] => b.updated match {
        case c: Board[y] => c.updated match {
          case d: Board[z] => d.updated
        }
      }
    }
  }
}

这是因为一旦您将未知类型绑定到类型变量 xyz,编译器就会被迫做一些额外的推理工作,并推断出它实际上必须是 x <: Board[_$?] 等等。不幸的是,它一次只进行一个步骤的推理,因为如果它试图计算最精确的类型,则类型计算将会发散。


上界和通用量化不一样

请注意,您的第一个变通方法只起作用两次:

object BoardOps_upperBound_once {
  def updated(board: Board[_<:Board[_]]) = {
    board.updated.updated // third .updated would not work
  }
}

因此,它不等同于你的第二个解决方法,它也可以无限次工作,这里的例子是 updated:

的三个调用
object BoardOps_genericT {
  def updated[T <: Board[T]](board: T) : T = {
    board.updated.updated.updated
  }
}