避免泛型类的低调

PNDC

注意到我的代码本质上是遍历列表并更新Map中的值,因此我首先创建了一个简单的帮助程序方法,该方法使用了一个函数来转换Map值并返回更新的Map。随着程序的发展,它还获得了其他一些Map转换功能,因此很自然地将其转换为一个隐式值类,该方法向中添加了方法scala.collection.immutable.Map[A, B]该版本工作正常。

但是,关于需要特定地图实现的方法没有什么要求,它们似乎适用于scala.collection.Map[A, B]甚至是MapLike因此,我希望它在映射类型以及键和值类型中通用。这就是所有梨形的地方。

我当前的迭代看起来像这样:

implicit class RichMap[A, B, MapType[A, B] <: collection.Map[A, B]](
    val self: MapType[A, B]
) extends AnyVal {
  def updatedWith(k: A, f: B => B): MapType[A, B] =
    self updated (k, f(self(k)))
}

由于self updated (k, f(self(k)))isascala.collection.Map[A, B]而不是,因此无法编译此代码MapType[A, B]换句话说,的传回类型self.updated就好像self的类型是上限类型边界,而不是实际声明的类型。

我可以通过向下转换来“修复”代码:

def updatedWith(k: A, f: B => B): MapType[A, B] =
  self.updated(k, f(self(k))).asInstanceOf[MapType[A, B]]

这并不令人满意,因为向下转换是一种代码异味,并指示类型系统的滥用。在这种特殊情况下,该值似乎始终是强制转换类型,并且整个程序在此向下转换的情况下都能正确编译并运行,从而支持此视图,但仍会闻起来。

因此,是否有更好的方式编写此代码以使scalac正确推断类型而无需使用下转换,或者这是编译器的限制,下转换是必要的吗?

[编辑添加以下内容。]

仍在探索一些想法,因此使用这种方法的代码有些复杂和混乱,但是一个最小的例子是频率分布的计算,其副作用大致类似于以下代码:

var counts = Map.empty[Int, Int] withDefaultValue 0
for (item <- items) {
  // loads of other gnarly item-processing code
  counts = counts updatedWith (count, 1 + _)
}

在撰写本文时,我的问题有三个答案。

归根结底就是让它反正updatedWith回来scala.collection.Map[A, B]从本质上讲,它采用的是我的原始版本,该版本接受并返回immutable.Map[A, B],并且使类型不太明确。换句话说,它仍然不够通用,并为调用者使用的类型设置了策略。我当然可以更改counts声明中的类型,但这也是一种代码味道,可以解决返回错误类型的库的问题,而它真正要做的就是将向下转换移到调用者的代码中。所以我一点都不喜欢这个答案。

另外两个是CanBuildFrom和构建器的变体,它们本质上是在地图上进行迭代以生成修改后的副本。一个内联了修改后的updated方法,而另一个内联调用了原始方法,updated并将其附加到了构建器中,因此似乎制作了额外的临时副本。两者都是解决类型正确性问题的好答案,尽管从性能的角度来看,避免额外复制的是两者中的更好者,因此我更喜欢这样做。然而,另一个更短,并且可以说更清楚地表明了意图。

在一个假设的不可变Map共享与List相似的大树的情况下,这种复制将破坏共享并降低性能,因此最好使用现有的modified而不执行复制。但是,Scala的不可变地图似乎没有做到这一点,因此复制(一次)似乎是一种务实的解决方案,在实践中不太可能有任何不同。

迈克尔·扎亚克(Michael Zajac)

是的!使用CanBuildFrom这就是Scala集合库使用CanBuildFrom证据推断最接近您想要的集合类型的方式只要您有的隐式证据CanBuildFrom[From, Elem, To]From开始的集合类型在哪里,集合包含的类型在哪里Elem,并且To是您想要的最终结果。CanBuildFrom会提供一个Builder可向其中添加元素,当你做,你可以打电话Builder#result()来获得适当类型的已完成收集。

在这种情况下:

From = MapType[A, B]
Elem = (A, B) // The type actually contained in maps
To = MapType[A, B]

执行:

import scala.collection.generic.CanBuildFrom

implicit class RichMap[A, B, MapType[A, B] <: collection.Map[A, B]](
    val self: MapType[A, B]
) extends AnyVal {
  def updatedWith(k: A, f: B => B)(implicit cbf: CanBuildFrom[MapType[A, B], (A, B), MapType[A, B]]): MapType[A, B] = {
    val builder = cbf()
    builder ++= self.updated(k, f(self(k)))
    builder.result()
  }
}

scala> val m = collection.concurrent.TrieMap(1 -> 2, 5 -> 3)
m: scala.collection.concurrent.TrieMap[Int,Int] = TrieMap(1 -> 2, 5 -> 3)

scala> m.updatedWith(1, _ + 10)
res1: scala.collection.concurrent.TrieMap[Int,Int] = TrieMap(1 -> 12, 5 -> 3)

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档