在参数化类中混合通用特征而不重复类型参数
假设我想创建一个可以混合到任何 Traversable[T] 中的特征。最后,我希望能够这样说:
val m = Map("name" -> "foo") with MoreFilterOperations
并且在 MoreFilterOperations 上拥有以 Traversable 提供的任何内容表达的方法,例如:
def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2
但是,问题显然是 T 没有定义为 MoreFilterOperations 上的类型参数。一旦我这样做了,它当然是可行的,但是我的代码将读取:
val m = Map("name" -> "foo") with MoreFilterOperations[(String,String)]
或者如果我定义了这种类型的变量:
var m2: Map[String,String] with MoreFilterOperations[(String,String)] = ...
这对我来说是冗长的方式。我希望以这样的方式定义该特征,以便我可以将后者写为:
var m2: Map[String,String] with MoreFilterOperations
我尝试了自我类型、抽象类型成员,但它没有产生任何有用的结果。有什么线索吗?
Let's assume I want to create a trait that I can mix in into any Traversable[T]. In the end, I want to be able to say things like:
val m = Map("name" -> "foo") with MoreFilterOperations
and have methods on MoreFilterOperations that are expressed in anything Traversable has to offer, such as:
def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2
However, the problem is clearly that T is not defined as a type parameter on MoreFilterOperations. Once I do that, it's doable of course, but then my code would read:
val m = Map("name" -> "foo") with MoreFilterOperations[(String,String)]
or if I define a variable of this type:
var m2: Map[String,String] with MoreFilterOperations[(String,String)] = ...
which is way to verbose for my taste. I would like to have the trait defined in such a way that I could write the latter as:
var m2: Map[String,String] with MoreFilterOperations
I tried self types, abstract type members, but it hasn't resulted in anything useful. Any clues?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Map("name" -> "foo")
是一个函数调用,而不是构造函数,这意味着您不能编写:您可以编写更多内容
要获得 mixin,您必须要使用具体类型,天真的第一次尝试将是这样的:
在此处使用工厂方法以避免重复类型参数。然而,这是行不通的,因为
++
方法只会返回一个普通的旧HashMap
,而没有 mixin!解决方案(正如 Sam 建议的那样)是使用隐式转换来添加 pimped 方法。这将允许您使用所有常用技术来转换地图,并且仍然能够在生成的地图上使用额外的方法。我通常会使用类而不是特征来执行此操作,因为可用的构造函数参数会导致更清晰的语法:
这允许您编写
但它仍然不能很好地与集合框架配合使用。您从地图开始,最终得到
Traversable
。事情不应该是这样的。这里的技巧是使用更高级的类型来抽象集合类型,足够简单。您必须提供
Repr
(表示集合的类型)和T
(元素类型)。我使用TraversableLike
而不是Traversable
因为它嵌入了它的表示;如果没有这个,无论起始类型如何,filterFirstTwo
都会返回一个Traversable
。现在是隐式转换。这是类型表示法中事情变得有点棘手的地方。首先,我使用更高级的类型来捕获集合的表示:
CC[X] <: Traversable[X]
,这参数化了CC
类型,它必须是 Traversable 的子类(注意这里使用X
作为占位符,CC[_] <: Traversable[_]
并不意味着同样的事情)。还有一个隐式的
CC[T] <:< TraversableLike[T,CC[T]]
,编译器使用它来静态保证我们的集合CC[T]
确实是TraversableLike
的子类,因此MoreFilterOperations
构造函数的有效参数:到目前为止,一切顺利。但仍然存在一个问题......它不适用于地图,因为它们采用两个类型参数。解决方案是使用与以前相同的原则向
MoreFilterOperations
对象添加另一个隐式:当您还想使用实际上不是集合但可以查看的类型时,真正的美妙就出现了就好像他们是一样。还记得
MoreFilterOperations
构造函数中的Repr <% TraversableLike
吗?这是一个视图绑定,并允许可以隐式转换为TraversableLike
的类型以及直接子类。字符串就是一个典型的例子:如果你现在在 REPL 上运行它:
Map 进去,Map 出来。字符串进去,字符串出来。等等...
我还没有尝试过使用
Stream
,或者Set
,或者Vector
,但是你可以确信如果这样做,它将返回与您开始时相同类型的集合。Map("name" -> "foo")
is a function invocation and not a constructor, this means that you can't write:any more that you can write
To get a mixin, you have to use a concrete type, a naive first attempt would be something like this:
Using a factory method here to avoid having to duplicate the type params. However, this won't work, because the
++
method is just going to return a plain oldHashMap
, without the mixin!The solution (as Sam suggested) is to use an implicit conversion to add the pimped method. This will allow you to transform the Map with all the usual techniques and still be able to use your extra methods on the resulting map. I'd normally do this with a class instead of a trait, as having constructor params available leads to a cleaner syntax:
This allows you to then write
But it still doesn't play nicely with the collections framework. You started with a Map and ended up with a
Traversable
. That isn't how things are supposed to work. The trick here is to also abstract over the collection type using higher-kinded typesSimple enough. You have to supply
Repr
, the type representing the collection, andT
, the type of elements. I useTraversableLike
instead ofTraversable
as it embeds its representation; without this,filterFirstTwo
would return aTraversable
regardless of the starting type.Now the implicit conversions. This is where things get a bit trickier in the type notation. First, I'm using a higher-kinded type to capture the representation of the collection:
CC[X] <: Traversable[X]
, this parameterises theCC
type, which must be a subclass of Traversable (note the use ofX
as a placeholder here,CC[_] <: Traversable[_]
does not mean the same thing).There's also an implicit
CC[T] <:< TraversableLike[T,CC[T]]
, which the compiler uses to statically guarantee that our collectionCC[T]
is genuinely a subclass ofTraversableLike
and so a valid argument for theMoreFilterOperations
constructor:So far, so good. But there's still one problem... It won't work with maps, because they take two type parameters. The solution is to add another implicit to the
MoreFilterOperations
object, using the same principles as before:The real beauty comes in when you also want to work with types that aren't actually collections, but can be viewed as though they were. Remember the
Repr <% TraversableLike
in theMoreFilterOperations
constructor? That's a view bound, and permits types that can be implicitly converted toTraversableLike
as well as direct subclasses. Strings are a classic example of this:If you now run it on the REPL:
Map goes in, Map comes out. String goes in, String comes out. etc...
I haven't tried it with a
Stream
yet, or aSet
, or aVector
, but you can be confident that if you did, it would return the same type of collection that you started with.这不完全是你所要求的,但你可以用隐式解决这个问题:
It's not quite what you asked for, but you can solve this problem with implicits:
Scala 标准库使用隐式来实现此目的。例如
“123”.toInt
。我认为在这种情况下这是最好的方法。否则,您将必须完全实现“具有附加操作的映射”,因为不可变集合需要创建新混合类的新实例。
对于可变集合,您可以执行以下操作:
我宁愿使用隐式。
Scala standard library uses implicits for this purpose. E.g.
"123".toInt
. I think its the best way in this case.Otherwise you'll have to go through full implementation of your "map with additional operations" since immutable collections require creation of new instances of your new mixed class.
With mutable collections you could do something like this:
I'd rather use implicits.