Map("name" -> "foo")
是函数调用而不是构造函数,这意味着您不能编写:
Map("name" -> "foo") with MoreFilterOperations
你还能写什么
val m = Map("name" -> "foo")
val m2 = m with MoreFilterOperations
要获得 mixin,您必须使用具体类型,天真的第一次尝试是这样的:
def EnhMap[K,V](entries: (K,V)*) =
new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries
在这里使用工厂方法来避免重复类型参数。但是,这是行不通的,因为该++
方法只会返回一个普通的 old HashMap
,没有 mixin!
解决方案(正如 Sam 建议的那样)是使用隐式转换来添加 pimped 方法。这将允许您使用所有常用技术转换地图,并且仍然能够在生成的地图上使用您的额外方法。我通常会使用类而不是特征来执行此操作,因为具有可用的构造函数参数会导致语法更简洁:
class MoreFilterOperations[T](t: Traversable[T]) {
def filterFirstTwo(f: (T) => Boolean) = t filter f take 2
}
object MoreFilterOperations {
implicit def traversableToFilterOps[T](t:Traversable[T]) =
new MoreFilterOperations(t)
}
这使您可以编写
val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
val m2 = m filterFirstTwo (_._1.startsWith("n"))
但它仍然不能很好地与集合框架配合使用。您以 Map 开始,以Traversable
. 这不是事情应该如何运作的方式。这里的技巧是还使用更高种类的类型对集合类型进行抽象
import collection.TraversableLike
class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) {
def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2
}
很简单。您必须提供Repr
表示集合T
的类型和元素的类型。我使用TraversableLike
而不是Traversable
因为它嵌入了它的表示;没有这个,无论起始类型如何,filterFirstTwo
都会返回 a 。Traversable
现在是隐式转换。这是在类型表示法中事情变得有点棘手的地方。首先,我使用了更高种类的类型来捕获集合的表示:CC[X] <: Traversable[X]
,这将CC
类型参数化,它必须是 Traversable 的子类(注意X
这里用作占位符,CC[_] <: Traversable[_]
并不意味着相同的东西)。
还有一个 implicit CC[T] <:< TraversableLike[T,CC[T]]
,编译器使用它来静态保证我们的集合CC[T]
是真正的子类,TraversableLike
因此是构造函数的有效参数MoreFilterOperations
:
object MoreFilterOperations {
implicit def traversableToFilterOps[CC[X] <: Traversable[X], T]
(xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) =
new MoreFilterOperations[CC[T], T](xs)
}
到现在为止还挺好。但是还有一个问题......它不适用于地图,因为它们采用两个类型参数。解决方案是在对象中添加另一个隐式MoreFilterOperations
,使用与之前相同的原则:
implicit def mapToFilterOps[CC[KX,VX] <: Map[KX,VX], K, V]
(xs: CC[K,V])(implicit witness: CC[K,V] <:< TraversableLike[(K,V),CC[K,V]]) =
new MoreFilterOperations[CC[K,V],(K,V)](xs)
当您还想使用实际上不是集合但可以被视为集合的类型时,真正的美就出现了。还记得构造函数Repr <% TraversableLike
中的吗?MoreFilterOperations
这是一个视图绑定,并允许可以隐式转换为的类型TraversableLike
以及直接子类。字符串是一个典型的例子:
implicit def stringToFilterOps
(xs: String)(implicit witness: String <%< TraversableLike[Char,String])
: MoreFilterOperations[String, Char] =
new MoreFilterOperations[String, Char](xs)
如果你现在在 REPL 上运行它:
val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
// m: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
// Map((name,foo), (name2,foo2), (name3,foo3))
val m2 = m filterFirstTwo (_._1.startsWith("n"))
// m2: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
// Map((name,foo), (name2,foo2))
"qaxfwcyebovjnbointofm" filterFirstTwo (_ < 'g')
//res5: String = af
地图进去,地图出来。字符串进去,字符串出来。ETC...
我还没有尝试使用 a Stream
yet 或 aSet
或 a Vector
,但您可以确信,如果您这样做了,它将返回与您开始时相同类型的集合。