Scala中F-bounded类型与存在类型的编译错误及等价方案验证
嘿,这个问题我太熟悉了,咱们一步步拆解来看~
首先先把你的问题背景理清楚:
你用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
这个报错的核心原因很明确:Board[_]是一个存在类型,编译器只知道board.updated返回某个未知类型_$1,但它没法确定这个未知类型也实现了Board特质(也就是拥有updated方法),所以第二次调用.updated自然就找不到对应的方法了。
现在你提到的两种解决方法,咱们先补全并分析它们的等价性:
方法一:使用嵌套存在类型
object BoardOps { def updated(board: Board[_ <: Board[_]]) = { board.updated.updated } }
方法二:使用F-bounded类型参数(更常用的标准写法)
object BoardOps { def updated[T <: Board[T]](board: Board[T]) = { board.updated.updated } }
这两种方法是否等价?
答案是:不等价,它们的类型约束和适用范围有明显区别:
方法一的约束更宽松
它接受任何Board实现,只要该实现的updated方法返回的是另一个Board(不管返回的Board是否符合严格的F-bounded约束)。比如下面这个非严格F-bounded的实现,依然能被方法一接受:class TempBoard extends Board[FinalBoard] { def updated: FinalBoard = new FinalBoard } class FinalBoard extends Board[FinalBoard] { def updated: FinalBoard = this }TempBoard的updated返回FinalBoard(一个符合F-bounded的类型),所以TempBoard属于Board[_ <: Board[_]],可以被方法一正常处理。方法二的约束更严格
它只接受严格符合F-bounded约束的Board实现——也就是T必须是Board[T]的子类。上面的TempBoard就无法被方法二接受,因为TempBoard的类型参数是FinalBoard,而非自身,不满足T <: Board[T]的约束。安全性与一致性差异
方法一虽然宽松,但因为它要求updated的返回值是Board[_],所以第二次调用.updated是安全的(编译器明确知道返回值有updated方法)。而方法二则通过F-bounded类型参数,确保了整个调用链的类型一致性,在编译期就能避免更多潜在的类型错误,更符合F-bounded设计的初衷。
简单总结:如果你的场景需要兼容非严格F-bounded的Board实现,方法一更合适;如果希望严格遵循F-bounded的类型设计、保证类型一致性,方法二更稳妥。
内容的提问来源于stack exchange,提问作者Yann Moisan

