Scala:在同一范围内键入类,默认和自定义隐式转换

时间:2017-10-02 14:39:27

标签: scala implicit-conversion typeclass

我想知道是否有一种优雅的方法可以在同一范围内同时使用默认的隐式转换和一些用户定义的自定义转换。我有以下用例:

  1. 假设我们有一个特征定义了一些二进制操作(例如PlusSupport,它定义了plus(x, y))泛型类型E
  2. 我们可以在可以转换为"+"的对象上添加PlusSupport语法,为此我们需要提供隐式转换
  3. 对于许多不同类型的PlusSupport,有很多默认的隐式转化,我们的库的用户总是将它们导入为例如import defaultConversions._(全部导入,不要多想)
  4. 现在,用户为已经具有implicit val customConversion = ...默认转化的某种类型添加了一些自定义隐式转化(import defaultConversions._)(此自定义转化可能是用户编写的,也可能是由第三方提供的图书馆com.3dparty.veryAdvancedConversions.AwesomePlus);用户希望使用他的自定义转换
  5. 以下是代码示例:

    trait A // some type A
    trait B // some type B
    // ... many other types goes here
    
    // some binary operation
    trait PlusSupport[E] {
      def plus(a: E, b: E): E
    }
    
    object defaultConversions {
      // default conversion of A to PlusSupport[A]
      implicit def mkPlusSupportForA: A => PlusSupport[A] = _ => (a1: A, a2: A) => a1
    
      // default conversion of B to PlusSupport[B]
      implicit def mkPlusSupportForB: B => PlusSupport[B] = _ => (b1: B, b2: B) => b1
    
      // ... many other conversions goes here
    }
    
    // + operator for elements with PlusSupport 
    class PlusOps[E](lhs: E)(plus: PlusSupport[E]) {
      def +(rhs: E): E = plus.plus(lhs, rhs)
    }
    
    // adds "+" syntax
    trait PlusSyntax {
      implicit def plusOps[E](lhs: E)(implicit mkPlusSupport: E => PlusSupport[E]): PlusOps[E]
      = new PlusOps[E](lhs)(mkPlusSupport(lhs))
    }
    
    object syntax extends PlusSyntax
    
    
    def main(args: Array[String]): Unit = {
      // import all default conversions for A, B, C, D etc. etc.
      import defaultConversions._
      import syntax._
    
      // setup my custom conversion for A
      implicit val myCustomPlusForA: A => PlusSupport[A] = _ => (a1: A, a2: A) => a2
    
      val a1: A = new A {}
      val a2: A = new A {}
      val b1: B = new B {}
      val b2: B = new B {}
    
      // myCustomPlusForA should be used
      println((a1 + a2) == a1)
      println((a1 + a2) == a2)
    
      // default conversion for B should be used
      println((b1 + b2) == b1)
      println((b1 + b2) == b2)
    
    }
    

    它无法编译并出现以下错误:

    Error:(52, 19) type mismatch;
    found   : A
    required: String
       println((a1 + a2) == a1)
    

    可以通过两种方式纠正代码:

    1. 我们可以移除implicit val myCustomPlusForA - 一切正常,默认隐式转换将来自defaultConversions ;但我们需要使用我的自定义转换,因此这不是一个选项

    2. 我们可以将import defaultConversions._更改为import defaultConversions.{everything except conversion for A},然后使用myCustomPlusForA;但这也是一个不好的选择,因为库的用户不会关心它(用户只想导入所有“默认值”并添加一些“自定义”,例如他可以使用implicit val myCustomPlusForA而不用{{1 }}关键字(所有编译都很好)而不仅仅是为了测试如何通过完全自定义来改变事物的方式来添加{{1>}

    3. 所以问题是如何修复代码,以便implicitimplicit都在同一范围内,编译器将使用import defaultConversions._?应该使用哪种代码模式来实现所需的行为?

      更新我到目前为止找到的解决方法是use default value隐式参数并完全删除implicit val myCustomPlusForA(甚至将myCustomPlusForA设为私有以避免其用户使用):

      import defaultConversions._

      但是在运行时进行检查看起来真的很奇怪,而所有信息在编译时都可用,编译器应该“替换”正确的转换。

1 个答案:

答案 0 :(得分:0)

正如@JesperNordenberg在评论中建议的那样,可以使用隐式优先级。因此,为了使工作正常,只需将方法从defaultConversions移动到PlusSupport的伴随对象,然后删除defaultConversions

trait PlusSupport[E] {
  def plus(a: E, b: E): E
}

// place default implicit conversions into companion object
private object PlusSupport {
  // default conversion of A to PlusSupport[A]
  implicit def mkPlusSupportForA: A => PlusSupport[A] = _ => (a1: A, a2: A) => a1

  // default conversion of B to PlusSupport[B]
  implicit def mkPlusSupportForB: B => PlusSupport[B] = _ => (b1: B, b2: B) => b1

  // ... many other conversions goes here
}


def main(args: Array[String]): Unit = {
  // no need to import from object PlusSupport

  import syntax._

  // setup my custom conversion for A
  implicit val myCustomPlusForA: A => PlusSupport[A] = _ => (a1: A, a2: A) => a2

  val a1: A = new A {}
  val a2: A = new A {}
  val b1: B = new B {}
  val b2: B = new B {}

  // myCustomPlusForA will be used
  println((a1 + a2) == a1)
  println((a1 + a2) == a2)

  // default conversion for B will be used
  println((b1 + b2) == b1)
  println((b1 + b2) == b2)

}