我可以“打扮我的图书馆”吗?与具有良好变体类型的 TraversableLike.map 类似物?
假设我想将 map
之类的功能添加到 Scala List
中,类似于 list mapmap f
的功能,它应用了函数 f
到 list
的每个元素两次。 (一个更严重的例子可能是实现并行或分布式地图,但我不想被那个方向的细节分散注意力。)
我的第一个方法是
object MapMap {
implicit def createFancyList[A](list: List[A]) = new Object {
def mapmap(f: A => A): List[A] = { list map { a: A => f(f(a)) } }
}
}
现在效果很好,
scala> import MapMap._
import MapMap._
scala> List(1,2,3) mapmap { _ + 1 }
res1: List[Int] = List(3, 4, 5)
当然它仅适用于 List< /code>s,我们没有理由不希望它适用于任何带有
map
函数的 Traverseable
,例如 Set
s或Stream
。所以第二次尝试看起来像
object MapMap2 {
implicit def createFancyTraversable[A](t: Traversable[A]) = new Object {
def mapmap(f: A => A): Traversable[A] = { t map { a: A => f(f(a)) } }
}
}
但是现在,当然,结果不能分配给 List[A]
:
scala> import MapMap2._
import MapMap2._
scala> val r: List[Int] = List(1,2,3) mapmap { _ + 1 }
<console>:9: error: type mismatch;
found : Traversable[Int]
required: List[Int]
有一些中间立场吗?我可以编写一个隐式转换,将一个方法添加到 Traversable 的所有子类中,并成功返回该类型的对象吗?
(我猜这涉及到理解可怕的 CanBuildFrom
特征,甚至可能是 breakout
!)
Suppose I want to add functionality like map
to a Scala List
, something along the lines of list mapmap f
, which applies the function f
to each element of list
twice. (A more serious example might be implementing a parallel or distributed map, but I don't want to get distracted by details in that direction.)
My first approach would be
object MapMap {
implicit def createFancyList[A](list: List[A]) = new Object {
def mapmap(f: A => A): List[A] = { list map { a: A => f(f(a)) } }
}
}
this now works great
scala> import MapMap._
import MapMap._
scala> List(1,2,3) mapmap { _ + 1 }
res1: List[Int] = List(3, 4, 5)
except of course it's only for List
s, and there's no reason we shouldn't want this to work for anything Traverseable
, with a map
function, e.g. Set
s or Stream
s. So the second attempt looks like
object MapMap2 {
implicit def createFancyTraversable[A](t: Traversable[A]) = new Object {
def mapmap(f: A => A): Traversable[A] = { t map { a: A => f(f(a)) } }
}
}
But now, of course, the result can't be assigned to a List[A]
:
scala> import MapMap2._
import MapMap2._
scala> val r: List[Int] = List(1,2,3) mapmap { _ + 1 }
<console>:9: error: type mismatch;
found : Traversable[Int]
required: List[Int]
Is there some middle ground? Can I write an implicit conversion that adds a method to all subclasses of Traversable, and successfully returns objects with that type?
(I'm guess this involves understanding the dreaded CanBuildFrom
trait, and maybe even breakout
!)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您不能对所有 Traversable 执行此操作,因为它们不保证地图返回比 Traversable 更具体的内容。请参阅下面的更新 2。更新
我添加了另一种 pimped 方法
mapToString
,以演示为什么TraversableW
接受两个类型参数,而不是像 Alexey 的解决方案中那样接受一个参数。参数CC是一个高级类型,它代表原始集合的容器类型。第二个参数A
表示原始集合的元素类型。因此,mapToString
方法能够返回具有不同元素类型的原始容器类型:CC[String
。更新2
感谢@oxbow_lakes 的评论,我重新考虑了这一点。确实可以直接pimp
CC[X] <: Traversable[X]
,TraversableLike
并不是严格需要的。内嵌评论:有什么区别?我们必须使用
collection.breakOut
,因为我们无法从单纯的Traversable[A]
恢复特定的集合子类型。Builder
b
使用原始集合进行初始化,这是通过map
保留动态类型的机制。然而,我们的CanBuildFrom
通过类型参数Nothing
否认了 From 的所有知识。对于Nothing
所能做的就是忽略它,这正是breakOut
所做的:我们不能调用
b.apply(from)
,就像您可以调用def foo(a: Nothing) = 0
一样。You can't do this for all Traversables, as they don't guarantee that map returns anything more specific than Traversable.See Update 2 below.UPDATE
I've added another pimped method,
mapToString
, to demonstrate whyTraversableW
accepts two type parameters, rather than one parameter as in Alexey's solution. The parameterCC
is a higher kinded type, it represents the container type of the original collection. The second parameter,A
, represents the element type of the original collection. The methodmapToString
is thus able to return the original container type with a different element type:CC[String
.UPDATE 2
Thanks to @oxbow_lakes comment, I've rethought this. It is indeed possible to directly pimp
CC[X] <: Traversable[X]
,TraversableLike
is not strictly needed. Comments inline:What's the difference? We had to use
collection.breakOut
, because we can't recover the specific collection subtype from a mereTraversable[A]
.The
Builder
b
is initialized with the original collection, which is the mechanism to preserve the dynamic type through amap
. However, ourCanBuildFrom
disavowed all knowledge of the From, by way of the type argumentNothing
. All you can do withNothing
is ignore it, which is exactly whatbreakOut
does:We can't call
b.apply(from)
, no more than you could calldef foo(a: Nothing) = 0
.作为一般规则,当您想要返回相同类型的对象时,您需要
TraversableLike
(IterableLike
、SeqLike
等)而不是可遍历
。这是我能想到的最通用的版本(单独的 FancyTraversable 类是为了避免推断结构类型和反射命中):As a general rule, when you want to return objects with the same type, you need
TraversableLike
(IterableLike
,SeqLike
, etc.) instead ofTraversable
. Here is the most general version I could come up with (the separateFancyTraversable
class is there to avoid inferring structural types and the reflection hit):