意外的“递归值需要类型”编译错误(由本地隐含触发)

时间:2017-08-27 09:44:56

标签: scala

我刚遇到一个意外的编译错误“递归值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

1 个答案:

答案 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的匹配项。