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]]
}
但是如果我希望它也适用于 TreeMap
s 和 SortedMap
s 以及其他任何东西怎么办?
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
特征通常会被类型 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)) }
}
}
我需要一个隐式的 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]]
}
但是如果我希望它也适用于 TreeMap
s 和 SortedMap
s 以及其他任何东西怎么办?
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
特征通常会被类型 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)) }
}
}