【问题标题】:nested polymorphism in scalascala中的嵌套多态性
【发布时间】:2023-03-13 12:37:01
【问题描述】:

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

我可以让它适用于所有 Map 类型...或多态值...但不能同时适用于两者。如果没有在隐式类中找不到方法的错误,我无法弄清楚如何组合成一个隐式类。

比如说……

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

但如果我希望它也适用于 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)但没有被应用。

允许这种嵌套多态吗?我什至不知道要搜索什么字词。

提前致谢。

【问题讨论】:

    标签: scala polymorphism implicit


    【解决方案1】:

    解决您的问题的关键是不要试图立即推断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...你们俩都给了我很多好东西要学习,以前从未见过临时多态性或类型类以及其他东西。我有很多要学习的。谢谢。
    【解决方案2】:

    除了 @francoisr 的提议,建议将您的 F 有界多态性 (B &lt;: 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" -&gt; Meta(1), "b" -&gt; Meta(2)).mergeMaps(Map("c" -&gt; Meta(3), "d" -&gt; 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)) }
      }
    }
    

    【讨论】:

    • 只是好奇:使用这种类型而不是高级类型有什么好处吗?并不是说这更糟,我只是想看看是否有这种方法更可取的情况。
    • @francoisr 好吧,f-bounds 与 type 类是众所周知的竞争方法tpolecat.github.io/2015/04/29/f-bounds.html 没有普遍的优势。如果您对灵活性感兴趣,类型类会更灵活一些。如果您对刚性感兴趣,F-bounds 会更严格一些。我的第一个修复只是对 OP 代码的最小修复。请注意我的第二次修复。我删除了asInstanceOf
    • @francoisr 请注意,F-Bound 也更脆弱,您可以轻松破坏它。此外,这只是一个 'Semigroup*.
    • @francoisr 因此,引入更高种类的类型是一个好主意(因为,正如我们所见,Map++ 这样的操作是这样定义的)。只是您还用类型类替换了 f-bounded Combinable,我之前的评论是关于这个的。
    • @kmh 因为如果A = CC[String, B] 然后A =&gt; CC[String, B], CC[String, B] =&gt; A 只是A =&gt; A 并且这样的隐式自动在一个范围内:def test[A]: Unit = { implicitly[A =&gt; A] } 编译。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    • 1970-01-01
    • 1970-01-01
    • 2017-07-17
    • 1970-01-01
    • 2017-06-11
    • 1970-01-01
    相关资源
    最近更新 更多