我需要一个隐式类,它有一个方法,可以让我合并任何可能具有重复键和多态值的不可变映射类型(<: Map
)。我无法弄清楚让隐式类使用嵌套的多态类型并隐式工作(类似于A <: Map[_, B], B <: Combinable[B]
)。
我可以让它适用于所有地图类型... 或多态值... 但不能两者兼而有之。 我无法弄清楚如何在没有错误的情况下组合成一个隐式类,因为它无法在隐式类中找到该方法。
所以例如...
trait Combinable[A] {
this: A =>
def combine(that: A): A
def combine(that: Option[A]): A = that match {
case Some(a) => this combine a
case None => this
}
}
所以假设我有一个班级...
case class Meta(???) extends Combinable[Meta] {
def combine(that: Meta): Meta = ???
}
现在,如果我有一个标准的不可变Map
,那就太轻而易举了...... 效果很好。
implicit class CombinableMaps[A <: Combinable[A]](val m1: Map[String, A]) {
def mergeMaps(m2: Map[String, A]): Map[String, A] = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}.asInstanceOf[Map[String, A]]
}
但是,如果我希望它也适用于TreeMap
和SortedMap
以及其他任何东西怎么办?
implicit class CombinableMaps[B <: Combinable[B], A <: Map[String,B]](val m1: A) {
def mergeMaps(m2: A): A = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}.asInstanceOf[A]
}
这编译没有错误,但是当我尝试使用mergeMap
方法时,它会抛出error: value mergeMaps is not a member of Map[String,Meta]
.
我尝试了一种变体,其中B
是一种传递给A
的类型,就像A[B]
...... 再次编译(如果我导入scala.language.higherKinds
),但没有应用。
允许这种嵌套多态性吗? 我什至不知道要搜索什么术语。
提前谢谢。
解决问题的关键是不要试图立即推断A
。即使是简单的事情也会使类型推断陷入困境:
class Foo[B, A <: Map[String, B]](val a: A)
new Foo(Map("foo" -> 42))
inferred type arguments [Nothing,scala.collection.immutable.Map[String,Int]] do not conform to value 's type parameter bounds [B,A <: Map[String,B]]
type mismatch;
found : scala.collection.immutable.Map[String,Int]
required: A
这是因为类型推断在"层"中工作:它在检查A
的"内部"之前解析A
和B
。解决方案是以不同的方式定义 A:
class Foo[B, A[X] <: Map[String, X]](val a: A[B])
new Foo(Map("foo" -> 42))
现在让我们看看你的问题。您的Combinable
特征通常会被类型类替换,这样您就不必在需要组合的每个类型A
上扩展Combinable
。您可以隐式地为所需的每种类型隐式提供Combinable
。这完全是可选的,如果你愿意,你可以选择坚持你的特质。
trait Combinable[A] {
def combine(first: A, second: A): A
def combine(first: A, second: Option[A]): A = second match {
case Some(a) => combine(first, a)
case None => first
}
}
case class Meta(x: Int)
object Meta {
implicit val combinableInstance: Combinable[Meta] = (first, second) => Meta(first.x + second.x)
}
implicit class CombinableMaps[B : Combinable, A[X] <: Map[String,X]](val m1: A[B]) {
def mergeMaps(m2: A[B]): A[B] = {
m1 ++
m2.map { case (k,v) => k -> implicitly[Combinable[B]].combine(v,m1.get(k)) }
}.asInstanceOf[A[B]]
}
import collection.immutable.TreeMap
val m = TreeMap("foo" -> Meta(42), "bar" -> Meta(43))
val m2 = TreeMap("bar" -> Meta(43))
val m3: TreeMap[String,Meta] = m.mergeMaps(m2)
除了@francoisr的建议,其中你的F界多态性(B <: Combinable[B]
)的方法被提议替换为临时多态性(Combinable
成为一个类型类),你也可以尝试用隐式约束替换边界。尝试替换
implicit class CombinableMaps[B <: Combinable[B], A <: Map[String, B]](val m1: A) {
def mergeMaps(m2: A): A = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}.asInstanceOf[A]
}
跟
implicit class CombinableMaps[A, B](val m1: A)(implicit
ev: A <:< Map[String, B],
ev1: B <:< Combinable[B]
) {
def mergeMaps(m2: A): A = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}.asInstanceOf[A]
}
或者只是
implicit class CombinableMaps[A, B <: Combinable[B]](val m1: A)(implicit
ev: A <:< Map[String,B]
) {
def mergeMaps(m2: A): A = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}.asInstanceOf[A]
}
然后Map("a" -> Meta(1), "b" -> Meta(2)).mergeMaps(Map("c" -> Meta(3), "d" -> Meta(4)))
编译[scastie]
我也建议删除这个丑陋的asInstanceOf[A]
[scastie]
import scala.collection.immutable.MapOps
implicit class CombinableMaps[A, B <: Combinable[B],
CC[K,+V] <: MapOps[K, V, CC, _]](val m1: A)(implicit
ev0: A => CC[String, B],
ev2: CC[String, B] => A,
) {
def mergeMaps(m2: A): A = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}
}
或者只是
implicit class CombinableMaps[B <: Combinable[B],
CC[K,+V] <: MapOps[K, V, CC, _]
](val m1: CC[String, B]){
def mergeMaps(m2: CC[String, B]): CC[String, B] = {
m1 ++
m2.map { case (k,v) => k -> (v combine m1.get(k)) }
}
}