【问题标题】:Infering generic type parameters in Scala在 Scala 中推断泛型类型参数
【发布时间】:2016-05-29 10:36:23
【问题描述】:

嗨,我一直在尝试统一嵌套地图的集合。 所以我想实现一个带有签名的方法:

def unifyMaps(seq: Seq[Map[String, Map[WordType, Int]]]): Map[String, Map[WordType, Int]]

(WordType 是一个 Java 枚举。)第一种方法是进行手动映射合并。

def unifyMapsManually(seq: IndexedSeq[Map[String, Map[WordType, Int]]]): Map[String, Map[WordType, Int]] = {
  seq reduce { (acc, newMap) =>
    acc ++ newMap.map { case (k, v) =>
      val nestedMap = acc.getOrElse(k, Map.empty)
      k -> (nestedMap ++ v.map { case (k2, v2) => k2 -> (nestedMap.getOrElse(k2, 0) + v2) })
    }
  }
}

它有效,但我在这里做的是递归应用完全相同的模式,所以我想我会制作一个递归通用版本。 第二种方法:

def unifyTwoMapsRecursively(m1: Map[String, Map[WordType, Int]], m2: Map[String, Map[WordType, Int]]): Map[String, Map[WordType, Int]] = {
  def unifyTwoMaps[K, V](nestedMapOps: (V, (V, V) => V))(m1: Map[K, V], m2: Map[K, V]): Map[K, V] = {
    nestedMapOps match {
      case (zero, add) =>
        m1 ++ m2.map { case (k, v) => k -> add(m1.getOrElse(k, zero), v) }
    }
  }
  val intOps = (0, (a: Int, b: Int) => a + b)
  val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps) _)
  unifyTwoMaps(mapOps)(m1, m2)
}

但它失败了:

Error:(90, 18) type mismatch;
 found   : (scala.collection.immutable.Map[pjn.wierzba.DictionaryCLP.WordType,Int], (Map[Nothing,Int], Map[Nothing,Int]) => Map[Nothing,Int])
 required: (scala.collection.immutable.Map[_ <: pjn.wierzba.DictionaryCLP.WordType, Int], (scala.collection.immutable.Map[_ <: pjn.wierzba.DictionaryCLP.WordType, Int], scala.collection.immutable.Map[_ <: pjn.wierzba.DictionaryCLP.WordType, Int]) => scala.collection.immutable.Map[_ <: pjn.wierzba.DictionaryCLP.WordType, Int])
    unifyTwoMaps(mapOps)(m1, m2)
                 ^

好吧,我不知道地图键的上限,但显然没有正确推断出柯里化函数。我对intOps 有类似的错误,所以我尝试提供确切的类型:

val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps)(_: Map[String, Map[WordType, Int]], _: Map[String, Map[WordType, Int]]))

但这一次它失败了:

Error:(89, 67) type mismatch;
 found   : Map[String,Map[pjn.wierzba.DictionaryCLP.WordType,Int]]
 required: Map[?,Int]
    val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps)(_: Map[String, Map[WordType, Int]], _: Map[String, Map[WordType, Int]]))
                                                                  ^

而这一次,我完全不知道接下来要尝试什么才能让它发挥作用。

编辑:我找到了问题的解决方案,但我仍然想知道为什么我在此代码 sn-p 中出现类型不匹配错误:

val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps) _)

根据this answer,scala 类型推断适用于每个参数列表 - 这正是我在这里为柯里化目的所做的。我的unifyTwoMaps 函数接受两个参数列表,我试图仅推断第二个。

【问题讨论】:

    标签: scala generics types type-inference currying


    【解决方案1】:

    通用递归解决方案的解决方案

    好的,在花了一上午的时间之后,我终于明白我一直提供错误的确切类型。

    val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps)(_: Map[String, Map[WordType, Int]], _: Map[String, Map[WordType, Int]]))
    

    应该是

    val mapOps = (Map.empty[WordType, Int], unifyTwoMaps(intOps)(_: Map[WordType, Int], _: Map[WordType, Int]))
    

    因为我需要传递 Map 的 VMap[WordType, Int] 的类型,而不是整个外部地图的类型。现在它可以工作了!

    嵌套地图合并底层问题的解决方案

    好吧,抽象地图上的 V zeroadd 应该敲响警钟,我一直在重新发明 Monoid。所以我想我会尝试来自this answer 的Scalaz |+| Semigroups 运算符解决方案。

    import scalaz.Scalaz._
    def unifyMapsWithScalaz(seq: Seq[Map[String, Map[WordType, Int]]]): Map[String, Map[WordType, Int]] = {
      seq reduce (_ |+| _)
    }
    

    而且它有效!

    有趣的是,我在尝试我的解决方案之前已经看过那篇文章,但我认为我不确定它是否适用于嵌套数据结构,尤其是我的地图键是 Java Enum 时。我想我必须提供一些扩展 Semigroups 类型类的自定义实现。
    但正如我在重新发明轮子实现过程中所证明的那样,枚举只需要作为传递的类型和映射键,它工作得很好。斯卡拉兹干得好!

    嗯,这实际上会是一篇很好的博客文章..

    编辑:但我仍然不明白为什么我首先会遇到这种类型推断问题,我已经更新了问题。

    【讨论】:

    • 如果你的序列是List,你也可以说Foldable[List].fold(seq)seq.concatenate,或seq.suml
    猜你喜欢
    • 1970-01-01
    • 2012-11-29
    • 1970-01-01
    • 2023-03-08
    • 2014-02-04
    • 2013-06-04
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    相关资源
    最近更新 更多