繁体   English   中英

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

[英]Scala: type classes, default and custom implicit conversions in the same scope

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

  1. 假设我们有一个特性定义了一些二进制操作(例如PlusSupport ,它定义了plus(x, y) )用于泛型类型E
  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 val myCustomPlusForA implicit关键字(所有编译)罚款)而不是添加implicit只是为了测试如何通过完全自定义更改事物)

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

更新:到目前为止我找到的解决方法是使用隐式参数的默认值并完全删除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))
}

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

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM