【问题标题】:Scala: How can an import prevent finding an implicit value?Scala:导入如何防止找到隐式值?
【发布时间】:2017-04-04 05:33:56
【问题描述】:

我可以使用建议来调试隐式:

我想使用隐式,x

type T
trait HasT {
  implicit def x: T = ...
}

但我还需要从某个包foo 导入通配符。我尝试了两种不同的方式来介绍两者:

class UseT extends HasT {
  import foo._
  implicitly[T] // fails! "could not find implicit value"
  // use foo stuff
}

class UseT {
  object hasT extends HasT
  import hasT.x
  import foo._
  implicitly[T] // fails! "could not find implicit value"
}

两者都以“找不到”(不是“模糊的隐含值”)失败。

当隐式标识符 x: T 可在方法调用点通过继承或导入访问时,就会发生这种情况。

我的解决方法是在导入之前将 x 重新绑定到隐式 val。以下两项工作:

implicit val x2: T = implicitly[T]
import foo._
implicitly[T] // works!

implicit val x2: T = x
import foo._
implicitly[T] // works!

foo 中的什么值可能会导致这种行为?

我的第一个猜测是foo 中存在一些竞争隐式,但如果它具有更高的优先级,那么下面的implicitly 仍然可以工作,如果它是一个模棱两可的隐式,我会得到一个不同的不同的错误。

编辑:Miles Sabin 的猜测是正确的!我发现隐含的阴影:timeColumnType。我仍然不完全理解这是如何工作的,因为 Som Snytt 观察到阴影隐式是通配符导入的(较低优先级),而阴影是继承的(最高优先级的一部分),所以我将把整个帖子留给后代。

撤回由miles sabin 提供的第二个猜测是implicit shadowing。我已经澄清了我的帖子以排除这种可能性。如果我尝试过package hasT extends HasT; import hasT._,这种情况与我的错误一致,但正如 som-snytt 指出的那样,这两种情况不会导致阴影。

在我的具体情况下,这可以通过更改我尝试使用的隐式名称来确认。
(这是错误的。在使用此测试进行验证时,我可能错过了publishLocalreload。)

context:我实际上是在尝试使用 slick。上面隐含的T其实是一个列类型映射:

import slick.driver.JdbcProfile

class Custom { ... } // stored as `Long` in postgres

trait ColumnTypes {
  val profile: JdbcProfile
  import profile.api._ // this is `foo` above
  type T = profile.BaseColumnType[Custom]
  implicit def customColumnType: T = 
    MappedColumnType.base[Custom, Long](_.toLong, Custom.fromLong)
}

class DatabaseSchema(val profile: JdbcProfile) extends ColumnTypes {
  // `implicitly[T]` does not fail here.
  import profile.api._ // this is also `foo` above
  // `implicitly[T]` fails here, but it's needed for the following:
  class CustomTable(tag: Tag) extends Table[Custom](tag, "CUSTOMS") {
    // following fails unless I rebind customColumnType to a local implicit
    def custom = column[Custom]("CUSTOM")
    def * = custom
  }
}

api/foo 的类型是JdbcProfile.API。冒犯的隐含可能是here,但我不知道为什么。我会尝试阻止其中一些被导入,看看是否可以缩小范围。

【问题讨论】:

  • 您的简化示例编译。它应该吗?我注意到您没有将T 别名为profile.api.BaseColu...。可能这也不重要。
  • @som-snytt 对!如果implicit 被命名为timeColumnType,它将不会编译。迈尔斯怀疑隐含阴影是正确的。
  • 我已经开始使用名称中的完整包路径来命名我的隐式,以避免将来发生这种情况。
  • 好的,谢谢。你的代码没有错……

标签: scala scope slick implicits shadowing


【解决方案1】:

我认为foo 最有可能包含一个名为x 的定义。当(通配符)从foo 导入时,它会影响本地定义,

scala> object foo { val x: Boolean = true }
defined object foo

scala> implicit val x: Int = 23
x: Int = 23

scala> implicitly[Int]
res0: Int = 23

scala> import foo._
import foo._

scala> implicitly[Int]
<console>:17: error: could not find implicit value for parameter e: Int
       implicitly[Int]
                 ^

【讨论】:

  • 通配符从不影响本地或继承或特定导入。 scala-lang.org/files/archive/spec/2.12/… 在这里,x 是模棱两可的,因为它是 import $line.x ; { import foo._ ; x },所以隐式不符合条件。
  • 确实 repl 为后续导入和失败的implicitly 引入了额外的范围,但这并不意味着阴影不对失败负责。
  • Stew 说x 是可访问的,所以f(x) 工作而不是获得x is ambiguous because both defined and imported。这就是我想要达到的目的。但也许“可访问”意味着“x 不是私有的”,而不是名称已正确绑定?
  • @som-snytt 提出了一个很好的观点。 “可访问”是指它在范围内而不是私有的——我尝试过的两种情况是显式导入它并继承。可以肯定的是,我尝试过重命名。我会尽快更新我的问题。
【解决方案2】:

这显然是隐式搜索中的一个错误。

首先,符合条件的所有标识符 x 都可以在 没有前缀的方法调用点,表示隐式 定义或隐式参数。因此,合格的标识符可以 是本地名称,或封闭模板的成员,或者它可能是 已通过导入子句无需前缀即可访问。

在示例中,不带前缀的x 指的是继承的符号。 X.x 没有前缀就无法访问。

隐式搜索正在摸索导入。

$ scala
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { def x: Int = 42 }

trait T { def x: Int = 17 }

object Y extends T {
  import X._
  def f = x
}

// Exiting paste mode, now interpreting.

defined object X
defined trait T
defined object Y

scala> Y.f
res0: Int = 17

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X._
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

<pastie>:19: error: could not find implicit value for parameter e: Int
         def f: Int = implicitly[Int]
                                ^

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X.{x => _, _}          // avoids bug, but is redundant
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

defined object X
defined trait T
defined object Y

scala> 

另一个使用 REPL 的例子是这样定义范围的:

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { def x: Int = 42 }
object Y { implicit def x: Int = 17 }
object Z {
  import Y.x
  def f = {
    import X._
    x
  }
}

// Exiting paste mode, now interpreting.

<pastie>:19: error: reference to x is ambiguous;
it is imported twice in the same scope by
import X._
and import Y.x
           x
           ^

x 根本不可用,隐式被正确排除。

只是为了确认:

$ scala -Xlog-implicits
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X._
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
         def f: Int = implicitly[Int]
                                ^
<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
         def f: Int = implicitly[Int]
                                ^
<console>:17: error: could not find implicit value for parameter e: Int
         def f: Int = implicitly[Int]
                                ^

scala> 

应该是https://issues.scala-lang.org/browse/SI-9208

【讨论】:

    猜你喜欢
    • 2019-08-12
    • 2010-09-10
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    相关资源
    最近更新 更多