让我们假设我想创建一个可以混入任何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
我尝试了自我类型,抽象类型成员,但它没有产生任何有用的东西。有线索吗?
答案 0 :(得分:11)
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
此处使用工厂方法以避免复制类型参数。但是,这不起作用,因为++
方法只会返回一个普通的HashMap
,而不是mixin!
解决方案(正如Sam所建议的)是使用隐式转换来添加pimped方法。这将允许您使用所有常用技术转换Map,并且仍然可以在生成的地图上使用您的额外方法。我通常用类而不是特性来做这个,因为有可用的构造函数params会导致更清晰的语法:
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"))
但它仍然不能很好地与集合框架。您从地图开始,最后得到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
将返回Traversable
,无论起始类型如何。
现在是隐式转换。这就是类型符号中的事情变得有点棘手的地方。首先,我使用更高级的类型来捕获集合的表示:CC[X] <: Traversable[X]
,这将参数化CC
类型,它必须是Traversable的子类(注意使用{{1在这里作为占位符,X
并不意味着相同的事情。)
还有一个隐式CC[_] <: Traversable[_]
,编译器使用它来静态地保证我们的集合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
如果您现在在REPL上运行它:
implicit def stringToFilterOps
(xs: String)(implicit witness: String <%< TraversableLike[Char,String])
: MoreFilterOperations[String, Char] =
new MoreFilterOperations[String, Char](xs)
地图进入,地图出来了。字符串进入,字符串出来。等...
我还没有使用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
或Stream
或Set
进行尝试,但您可以确信,如果您这样做,它将返回相同的类型您开始使用的集合。
答案 1 :(得分:2)
这不是你要求的,但你可以用implicits来解决这个问题:
trait MoreFilterOperations[T] {
def filterFirstTwo(f: (T) => Boolean) = traversable.filter(f) take 2
def traversable:Traversable[T]
}
object FilterImplicits {
implicit def traversableToFilterOps[T](t:Traversable[T]) = new MoreFilterOperations[T] { val traversable = t }
}
object test {
import FilterImplicits._
val m = Map("name" -> "foo", "name2" -> "foo2", "name3" -> "foo3")
val r = m.filterFirstTwo(_._1.startsWith("n"))
}
scala> test.r
res2: Traversable[(java.lang.String, java.lang.String)] = Map((name,foo), (name2,foo2))
答案 2 :(得分:1)
Scala标准库为此目的使用了implicits。例如。 "123".toInt
。在这种情况下,我认为这是最好的方式。
否则,您将不得不完全实施“带有其他操作的地图”,因为不可变集合需要创建新混合类的新实例。
使用可变集合,您可以执行以下操作:
object FooBar {
trait MoreFilterOperations[T] {
this: Traversable[T] =>
def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2
}
object moreFilterOperations {
def ~:[K, V](m: Map[K, V]) = new collection.mutable.HashMap[K, V] with MoreFilterOperations[(K, V)] {
this ++= m
}
}
def main(args: Array[String]) {
val m = Map("a" -> 1, "b" -> 2, "c" -> 3) ~: moreFilterOperations
println(m.filterFirstTwo(_ => true))
}
}
我宁愿使用暗示。