【问题标题】:Scala: Multiple implicit conversions with same nameScala:同名的多个隐式转换
【发布时间】:2014-02-28 21:11:04
【问题描述】:

使用 scala 2.10.3,我的目标是完成以下工作:

object A {
  implicit class Imp(i: Int) {
    def myPrint() {
      println(i)
    }
  }
}

object B {
  implicit class Imp(i: String) {
    def myPrint() {
      println(i)
    }
  }
}

import A._
import B._

object MyApp extends App {
  3.myPrint()
}

这失败了

value myPrint is not a member of Int

如果我给 A.Imp 和 B.Imp 赋予不同的名称(例如 A.Imp1 和 B.Imp2),它会起作用。

再深入一点,隐式转换似乎也存在同样的问题。

这行得通:

object A {
  implicit def Imp(i: Int) = new {
    def myPrint() {
      println(i)
    }
  }

  implicit def Imp(i: String) = new {
    def myPrint() {
      println(i)
    }
  }
}

import A._

object MyApp extends App {
  3.myPrint()
}

而这不是:

object A {
  implicit def Imp(i: Int) = new {
    def myPrint() {
      println(i)
    }
  }
}

object B {
  implicit def Imp(i: String) = new {
    def myPrint() {
      println(i)
    }
  }
}

import A._
import B._

object MyApp extends App {
  3.myPrint()
}

为什么?这是scala编译器中的错误吗?我需要这种情况,因为我的对象 A 和 B 派生自相同的特征(带有类型参数),然后定义了其类型参数的隐式转换。在这个特性中,我只能给隐式转换一个名字。我希望能够将更多这些对象导入我的范围。有没有办法做到这一点?

编辑:我不能给隐式类起不同的名字,因为上面的例子只是解决问题。我的实际代码看起来更像

trait P[T] {
  implicit class Imp(i: T) {
    def myPrint() {
      ...
    }
  }
}

object A extends P[Int]
object B extends P[String]

import A._
import B._

【问题讨论】:

  • 听起来您所需要的只是成员函数的名称相同,而不是隐式名称本身。为什么你需要同时调用“Imp”?
  • 我只有一个地方可以定义隐式。这是在通用父特征中。所以我只能给它一个名字。然后所有对象都从该特征继承并允许将其用于不同的类型。

标签: scala implicit-conversion scala-2.10


【解决方案1】:

隐式只需要作为一个简单的名称使用,因此您可以在导入时重命名。

只是为了验证:

scala> import A._ ; import B.{ Imp => BImp, _ }
import A._
import B.{Imp=>BImp, _}

scala> 3.myPrint
3

【讨论】:

  • @Joan 这是一个功能而不是错误,因为它是您禁用可能在范围内的隐式的方式。 (或者这是一种方法。)
  • 我不同意,我们应该能够导入 com.lib1.RichString.% 和 com.lib2.RichString./ 并将它们一起使用,因为在范围内没有函数名称冲突。对我来说,我可以在编译器中看到只有类名没有包名作为键的哈希映射。
  • @Joan 如果RichString 是隐式类,则必须重命名lib2.{RichString => RichString2} 才能看到这两种转换。请参阅我对implicit import 的票证的疯狂建议,以进行这种修改。
【解决方案2】:

其实,如果你替换它就可以了

import A._
import B._

import B._
import A._

我认为,发生的事情是 A.Imp 被 B.Imp 遮蔽,因为它具有相同的名称。显然,阴影适用于函数的名称,并且不考虑签名。 所以如果你导入A然后B,那么只有B.Imp(i:String)可用,如果你导入B然后A,那么只有A.Imp(i:Int)可用。

如果您需要同时使用 A.Imp 和 B.Imp,则必须重命名其中一个。

【讨论】:

  • 不确定您尝试了什么,但这不是它的工作原理。 scala-lang.org/files/archive/spec/2.11/…
  • 确实,我在 scala shell 中试过这个。那里的行为似乎有所不同。
  • 确实,是的。如果导入在同一输入行上,则在 REPL 中很重要;以及如何处理来自历史的进口(前几行)是有争议的。 :(
【解决方案3】:

万一其他人遇到这个问题,我在这里找到了部分解决方法:
https://github.com/lihaoyi/scalatags/blob/3dea48c42c5581329e363d8c3f587c2c50d92f85/scalatags/shared/src/main/scala/scalatags/generic/Bundle.scala#L120

那段代码是李浩毅写的,所以你可以确信没有更好的解决方案......

本质上,我们仍然可以使用特征来定义方法,但是需要样板来将这些隐式复制到唯一的名称中。这个例子可能更容易理解:

trait Chainable
object Chainable {
  implicit val _chainableFromInt     = IntChainable.chainable _
  implicit val _chainableFromIntTrav = IntChainable.traversable _
  implicit val _chainableFromIntOpt  = IntChainable.optional _
  implicit val _chainableFromIntTry  = IntChainable.tried _
  implicit val _chainableFromDom     = DomChainable.chainable _
  implicit val _chainableFromDomTrav = DomChainable.traversable _
  implicit val _chainableFromDomOpt  = DomChainable.optional _
  implicit val _chainableFromDomTry  = DomChainable.tried _
  private object IntChainable extends ImplChainables[Int] {
    def chainable(n:Int) = Constant(n)
  }
  private object DomChainable extends ImplChainables[dom.Element]{
    def chainable(e:Element) = Insertion(e)
  }
  private trait ImplChainables[T] {
    def chainable(t:T):Chainable
    def traversable(trav:TraversableOnce[T]):Chainable =
      SeqChainable(trav.map(chainable).toList)
    def optional(opt:Option[T]):Chainable =
      opt match {
        case Some(t) => chainable(t)
        case None => NoneChainable
      }
    def tried(tried:Try[T]):Chainable =
      optional(tried.toOption)
  }
}

换句话说,永远不要写:

trait P[T] {
  implicit def foo(i: T) = ...
}
object A extends P[X]

因为在类型参数化特征中定义隐式会导致这些命名冲突。尽管顺便提一下,上面链接中提到的特征在许多类型上都被参数化了,但想法是,在同一范围内不需要该特征的任何实现。 (JSDom vs Text,以及所有._ vs short._ 对于那些熟悉 Scalatags 的人)

我也推荐阅读:http://www.lihaoyi.com/post/ImplicitDesignPatternsinScala.html
它没有专门解决这个问题,但是对如何使用隐式进行了很好的总结。

然而,将所有这些部分放在一起,这似乎仍然是一个问题:

trait ImplChainables[AnotherTypeClass]{
  type F[A] = A=>AnotherTypeClass
  implicit def transitiveImplicit[A:F](t: A):Chainable
  implicit def traversable[A:F](trav: TraversableOnce[A]):Chainable = ...
}

这个特征允许的是:

object anotherImpl extends ImplChainables[AnotherTypeClass] {...}
import anotherImpl._
implicit val string2another: String=>AnotherTypeClass = ...
Seq("x"):Chainable

由于类型参数和上下文绑定(隐式参数),它们不能被 eta 扩展(即: Foo.bar _ )为函数值。隐式参数部分已在 Dotty 中修复:http://dotty.epfl.ch/blog/2016/12/05/implicit-function-types.html

我不知道是否有可能提供完整的解决方案,不用说这是一个复杂的问题。因此,如果相同的名称隐式可以正常工作并且可以避免整个问题,那就太好了。在任何情况下,添加一个 unimport 关键字都比通过隐藏它们来关闭隐式更有意义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-25
    • 2018-02-01
    • 2019-04-01
    相关资源
    最近更新 更多