Scala:具有相同名称的多个隐式转换

时间:2014-02-28 21:11:05

标签: scala implicit-conversion scala-2.10

使用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._

3 个答案:

答案 0 :(得分:8)

隐含必须以简单名称提供,因此您可以在导入时重命名。

只是为了验证:

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

scala> 3.myPrint
3

答案 1 :(得分:0)

实际上,如果你替换

,它会起作用
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,则必须重命名其中一个。

答案 2 :(得分:0)

如果有其他人遇到此问题,我会在此处找到部分解决方法:
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]

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

我还建议阅读: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

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

我不知道是否可以提供完整的解决方案,不用说这是一个复杂的问题。如果相同的名称暗示只是有效并且可以避免整个问题,那将是很好的。在任何情况下,添加一个unimport关键字比通过遮蔽它们来关闭implicits更有意义。