【问题标题】:Scala: type classes, default and custom implicit conversions in the same scopeScala:同一范围内的类型类、默认和自定义隐式转换
【发布时间】:2017-10-02 14:39:26
【问题描述】:

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

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

下面是代码示例:

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 而不使用 implicit 关键字(所有编译都很好),而不是添加 implicit 只是为了测试如何通过完全自定义来改变)

所以问题是如何修复代码,使import defaultConversions._implicit val myCustomPlusForA 都在同一个范围内,而myCustomPlusForA 将被编译器使用?应该使用哪种 Code Pattern 来实现所需的行为?

更新:到目前为止,我发现的解决方法是将 use default value 用于隐式参数并完全删除 import defaultConversions._(甚至将 defaultConversions 设为私有以避免用户使用它):

private def defaultMk[E](ev: E): E => PlusSupport[E] = ev match {
  case _: A => mkPlusSupportForA.asInstanceOf[E => PlusSupport[E]]
  case _: B => mkPlusSupportForB.asInstanceOf[E => PlusSupport[E]]
  case _ => ???
}

trait PlusSyntax {
  implicit def plusOps[E](lhs: E)(implicit mkPlusSupport: E => PlusSupport[E]
    = defaultConversions.defaultMk(lhs)): PlusOps[E] = new PlusOps[E](lhs)(mkPlusSupport(lhs))
}

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

【问题讨论】:

  • 您是否尝试过隐式优先级,请参阅this question
  • 我在考虑解决方案时看到了这个问题,但我不知道如何调整隐式优先级来解决我的问题
  • 隐式转换通常是邪恶的
  • @JesperNordenberg 你是对的 - 只是玩了优先级并解决了问题。谢谢!

标签: scala implicit-conversion typeclass


【解决方案1】:

正如@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)

}

【讨论】:

    猜你喜欢
    • 2017-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-26
    • 1970-01-01
    • 1970-01-01
    • 2011-08-30
    相关资源
    最近更新 更多