[英]Scala: type classes, default and custom implicit conversions in the same scope
我想知道是否有一种优雅的方法可以在同一范围内同时使用默认的隐式转换和一些用户定义的自定义转换。 我有以下用例:
PlusSupport
,它定义了plus(x, y)
)用于泛型类型E
PlusSupport
对象上添加"+"
语法,为此我们需要提供隐式转换 PlusSupport
有很多默认的隐式转换,我们库的用户总是将它们导入为例如import defaultConversions._
(导入所有并且不要考虑很多) 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)
代码可以通过两种方式进行纠正:
我们可以删除implicit val myCustomPlusForA
- 一切都会正常工作,并且将使用defaultConversions
的默认隐式转换 ; 但我们需要使用我的自定义转换,所以这不是一个选项
我们可以更改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.