在参数化类中混合通用特征而不重复类型参数

发布于 2024-10-17 13:43:28 字数 781 浏览 8 评论 0原文

假设我想创建一个可以混合到任何 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梦在深巷 2024-10-24 13:43:28

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 方法。这将允许您使用所有常用技术来转换地图,并且仍然能够在生成的地图上使用额外的方法。我通常会使用类而不是特征来执行此操作,因为可用的构造函数参数会导致更清晰的语法:

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 的子类(注意这里使用 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)

当您还想使用实际上不是集合但可以查看的类型时,真正的美妙就出现了就好像他们是一样。还记得 MoreFilterOperations 构造函数中的 Repr <% TraversableLike 吗?这是一个视图绑定,并允许可以隐式转换为 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

Map 进去,Map 出来。字符串进去,字符串出来。等等...

我还没有尝试过使用Stream,或者Set,或者Vector,但是你可以确信如果这样做,它将返回与您开始时相同类型的集合。

Map("name" -> "foo") is a function invocation and not a constructor, this means that you can't write:

Map("name" -> "foo") with MoreFilterOperations

any more that you can write

val m = Map("name" -> "foo")
val m2 = m with MoreFilterOperations

To get a mixin, you have to use a concrete type, a naive first attempt would be something like this:

def EnhMap[K,V](entries: (K,V)*) =
  new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries

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 old HashMap, 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:

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)
}

This allows you to then write

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
val m2 = m filterFirstTwo (_._1.startsWith("n"))

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 types

import collection.TraversableLike

class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) {
  def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2
}

Simple enough. You have to supply Repr, the type representing the collection, and T, the type of elements. I use TraversableLike instead of Traversable as it embeds its representation; without this, filterFirstTwo would return a Traversable 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 the CC type, which must be a subclass of Traversable (note the use of X 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 collection CC[T] is genuinely a subclass of TraversableLike and so a valid argument for the MoreFilterOperations constructor:

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)
}

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:

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)

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 the MoreFilterOperations constructor? That's a view bound, and permits types that can be implicitly converted to TraversableLike as well as direct subclasses. Strings are a classic example of this:

implicit def stringToFilterOps
(xs: String)(implicit witness: String <%< TraversableLike[Char,String])
: MoreFilterOperations[String, Char] =
  new MoreFilterOperations[String, Char](xs)

If you now run it on the 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

Map goes in, Map comes out. String goes in, String comes out. etc...

I haven't tried it with a Stream yet, or a Set, or a Vector, but you can be confident that if you did, it would return the same type of collection that you started with.

演出会有结束 2024-10-24 13:43:28

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

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))

It's not quite what you asked for, but you can solve this problem with 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))
温柔一刀 2024-10-24 13:43:28

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))
  }
}

我宁愿使用隐式。

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:

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))
  }
}

I'd rather use implicits.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文