Scala中的编译问题,具有F-bounded类型和存在类型

时间:2018-03-31 14:18:43

标签: scala types type-systems existential-type f-bounded-polymorphism

我使用F-bounded类型以便能够返回当前类型

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

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

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

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

错误为value updated is not a member of _$1

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

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

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

2 个答案:

答案 0 :(得分:5)

  

以下为什么不编译?

使用Board[_]作为参数类型告诉编译器&#34;我不关心板内的参数类型&#34;。也就是说,对编译器来说,这是一种存在类型,它不知道有关该类型的任何细节。因此,board.updated会返回&#34;不可言换的&#34;或 opaque 类型,因为我们告诉编译器&#34;扔掉&#34;那种类型的信息。

  

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

您之前的示例使用带有约束的存在类型作为Board[_]的子类型,或者更正式地编写:

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

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

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

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

答案 1 :(得分:0)

它不编译,因为只要你编写Board[_],编译器就不会推断出有关匿名类型参数_的任何有用信息。

有几种解决方法(与您已经提出的方法不同):

  1. 使用Board[X] forSome { type X <: Board[X] }
  2. 使用模式匹配来推断有关类型的更多信息
  3. 使用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
      }
    }