4

假设我想创建一个可以混入任何 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

我尝试了自我类型,抽象类型成员,但它没有产生任何有用的东西。有什么线索吗?

4

3 回答 3

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

在这里使用工厂方法来避免重复类型参数。但是,这是行不通的,因为该++方法只会返回一个普通的 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 Streamyet 或 aSet或 a Vector,但您可以确信,如果您这样做了,它将返回与您开始时相同类型的集合。

于 2011-02-17T11:29:06.993 回答
2

这不是你所要求的,但你可以用隐式解决这个问题:

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))
于 2011-02-17T07:35:36.813 回答
1

Scala 标准库为此目的使用了隐式。例如"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))
  }
}

我宁愿使用隐式。

于 2011-02-17T10:28:11.220 回答