Scala 中的嵌套多态性



我需要一个隐式类,它有一个方法,可以让我合并任何可能具有重复键和多态值的不可变映射类型(<: 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]]
}

但是,如果我希望它也适用于TreeMapSortedMap以及其他任何东西怎么办?

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的"内部"之前解析AB。解决方案是以不同的方式定义 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)) }
}
}

最新更新