Scala中的嵌套多态性

nested polymorphism in scala

我需要一个隐式的 class,其方法允许我合并任何可能具有重复键和多态值的不可变映射类型 (<: Map)。我无法弄清楚如何使用隐式 class 来使用嵌套多态类型并隐式工作(类似于 A <: Map[_, B], B <: Combinable[B])。

我可以让它在所有 Map 类型上工作...或在多态值上工作...但不能同时工作。我无法弄清楚如何组合成一个隐式 class 而不会出现无法在隐式 class.

中找到方法的错误

例如……

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
  }
}

假设我有一个 class...

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]]
}

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

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 特征通常会被类型 class 替换,这样您就不必在需要组合的每个类型 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-bounded 多态性方法 (B <: Combinable[B]) 被提议替换为临时多态性 (Combinable 变成类型 class),你也可以尝试用隐式约束替换边界。尝试替换

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)) }
  }
}