我刚遇到一个意外的编译错误“递归值xx需要类型”。为了简化,我想出了以下不能用Scala 2.12.3编译的代码:
class Wrapped[A](val inner: A)(implicit val ctag: ClassTag[A]){
type Inner = A
def classInfo: String = ctag.toString
}
object Implicits {
val W = new Wrapped(1234)
implicit val InnerSeq: Seq[W.Inner] = Seq(1,2)
implicit val InnerSet: Set[W.Inner] = Set(1,2) // Error: recursive value W needs type
}
我认为W
的类型定义明确。但似乎由于类型W
出现在本地隐式值的返回类型中,W
被认为是递归的,即使在创建W
时没有使用含义。
奇怪的是删除第二个值InnerSet
会使代码编译。因此,一个隐含的val引用W.Inner
是可以的,而两个或多个是禁止的。
删除Wrapper的隐式ClassTag
依赖项也会使代码编译。
通过将W移动到单独的特征很容易解决问题,但是知道这里发生了什么会很好。为什么代码只用一个隐含的范围编译?
更新
在Dotty的最后一个预览版本中,编译器的行为更加一致,并且根本不允许在范围内引用W.inner的任何含义:
[error] -- [E045] Syntax Error:
[error] 9 | implicit val InnerSeq: Seq[W.Inner] = Seq(1,2)
[error] | ^
[error] | cyclic reference involving value W
[error] one error found
答案 0 :(得分:2)
这是由于您定义的局部隐含的范围,类型推断如何工作以及隐式解析如何工作而发生的。如果我们查看typer阶段的输出(使用Ytyper-debug
),我们会看到:
|-- new Wrapped(1234) EXPRmode (site: value W in Implicits)
| |-- new Wrapped BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value W in Implicits)
| | |-- new Wrapped EXPRmode-POLYmode-QUALmode (silent: value W in Implicits)
| | | |-- Wrapped FUNmode-TYPEmode (silent: value W in Implicits)
| | | | \-> Wrapped
| | | \-> Wrapped[A]
| | \-> (inner: A)(implicit ctag: scala.reflect.ClassTag[A])Wrapped[A]
| |-- 1234 BYVALmode-EXPRmode-POLYmode (site: value W in Implicits)
| | \-> Int(1234)
| solving for (A: ?A)
| [search #1] start `(inner: A)(implicit ctag: scala.reflect.ClassTag[A])Wrapped[A]`, searching for adaptation to pt=scala.reflect.ClassTag[Int] (silent: value W in Implicits) implicits disabled
| |-- Seq[W.Inner] TYPEmode (site: value InnerSeq in Implicits)
| | |-- scala.`package` EXPRmode-POLYmode-QUALmode (site: value InnerSeq in Implicits)
| | | \-> scala.type
| | |-- W.Inner TYPEmode (site: value InnerSeq in Implicits)
| | | |-- W EXPRmode-POLYmode-QUALmode (site: value InnerSeq in Implicits)
| | | | |-- new Wrapped(1234) EXPRmode (site: value W in Implicits)
| | | | | |-- new Wrapped BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value W in Implicits)
| | | | | | |-- new Wrapped EXPRmode-POLYmode-QUALmode (silent: value W in Implicits)
| | | | | | | \-> Wrapped[A]
| | | | | | \-> (inner: A)(implicit ctag: scala.reflect.ClassTag[A])Wrapped[A]
| | | | | solving for (A: ?A)
| | | | | [search #2] start `(inner: A)(implicit ctag: scala.reflect.ClassTag[A])Wrapped[A]`, searching for adaptation to pt=scala.reflect.ClassTag[Int] (silent: value W
isabled
| | | | | |-- Set[W.Inner] TYPEmode (site: value InnerSet in Implicits)
| | | | | | |-- W.Inner TYPEmode (site: value InnerSet in Implicits)
| | | | | | | |-- W EXPRmode-POLYmode-QUALmode (site: value InnerSet in Implicits)
| | | | | | | | caught scala.reflect.internal.Symbols$CyclicReference: illegal cyclic reference involving value W: while typing W
首先必须弄清楚A
Wrapper
的类型参数,因为我们没有提供显式类型,编译器会将其视为Wrapper[A]
。编译器发现在范围内需要隐式ClassTag[A]
才能创建Wrapper
的实例,因此隐式解析会启动.Scala编译器将从本地范围的隐含开始,即{{1}和你定义的InnerSeq
一样。继续隐式搜索,编译器查看InnerSet
并尝试查看它是否适合InnerSeq
类型的解析,并且它是对应的A
,但是ClassTag[A]
定义在InnerSeq
的术语,因此编译器必须查看W.Inner
并查看它的基础类型,当前我们在开头看到它是W
。由于隐式解析是递归,它现在开始查找隐式范围,这可以帮助它推断Wrapper[A]
,下一个隐式定义是Wrapper[A]
,但它也是用`W.Inner来定义!因此我们有一个循环引用,编译器挽救了。
现在,当我们为InnerSet
:
W
类型检查器知道val W: Wrapped[Int] = new Wrapped(1234)
是W.type
而不是Wrapped[Int]
,因此它不需要做任何递归隐式解析。我们可以在调试输出中再次看到这一点:
Wrapped[A]
由于我们现在知道基础|-- Seq[W.Inner] TYPEmode (site: value InnerSeq in Implicits)
| |-- scala.`package` EXPRmode-POLYmode-QUALmode (site: value InnerSeq in Implicits)
| | \-> scala.type
| |-- W.Inner TYPEmode (site: value InnerSeq in Implicits)
| | |-- W EXPRmode-POLYmode-QUALmode (site: value InnerSeq in Implicits)
| | | \-> com.testing.SOTesting.Implicits.W.type (with underlying type Wrapped[Int]) <---- This is the difference
| | [adapt] A is now a TypeTree(com.testing.SOTesting.Implicits.W.Inner)
| | \-> com.testing.SOTesting.Implicits.W.Inner
| \-> Seq[com.testing.SOTesting.Implicits.W.Inner]
,因此typer可以将W.type
绑定到W.Inner
,从而找到Int
的匹配项。