scala – 避免泛型类中的向下倾斜
注意到我的代码实际上是在列表上迭代并更新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))) } 此代码无法编译,因为自更新(k,f(self(k)))是scala.collection.Map [A??,B],它不是MapType [A,B].换句话说,self.updated的返回类型就好像self的类型是上层类型而不是实际声明的类型. 我可以通过向下转换“修复”代码: def updatedWith(k: 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??,并使该类型不那么具体.换句话说,它仍然不够通用,并为调用者使用的类型设置策略.我当然可以更改计数声明中的类型,但这也是一个代码气味来解决返回错误类型的库,而它真正做的就是将向下转换为调用者的代码.所以我根本不喜欢这个答案. 另外两个是CanBuildFrom和构建器的变体,因为它们基本上遍历地图以生成修改后的副本.一个内联修改的更新方法,而另一个调用原始更新并将其附加到构建器,因此似乎进行了额外的临时副本.两者都是解决类型正确性问题的好答案,尽管从性能角度来看,避免额外复制的问题更好,我更喜欢这个问题.然而另一个更短,可以说更清楚地表明意图. 在假设的不可变映射的情况下,它以与List类似的方式共享大树,这种复制会破坏共享并降低性能,因此最好使用现有的修改而不执行复制.但是,Scala的不可变映射似乎并没有这样做,因此复制(一次)似乎是一种实用的解决方案,不太可能在实践中产生任何影响. 解决方法
是!使用
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,f: B => B)(implicit cbf: CanBuildFrom[MapType[A,(A,B),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) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |