在参数化的类中混合通用特征而无需复制类型参数 [英] Mixing in generic traits in parameterized classes without duplicating type parameters

查看:62
本文介绍了在参数化的类中混合通用特征而无需复制类型参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们假设我想创建一个可以混入任何Traversable [T]的特征.最后,我希望能够说些类似的话:

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

并在MoreFilterOperations上具有以Traversable必须提供的任何形式表示的方法,例如:

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

但是,问题显然是T没有在MoreFilterOperations上定义为类型参数.一旦我做到了,那当然是可行的,但是我的代码将显示为:

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?

推荐答案

Map("name" -> "foo")是函数调用而不是构造函数,这意味着您不能编写:

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

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

您还能写的任何东西

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

要获得mixin,您必须使用具体类型,天真的第一次尝试就是这样:

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

在这里使用工厂方法来避免必须复制类型参数.但是,这将不起作用,因为++方法将只返回一个普通的旧HashMap,而没有mixin!

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!

解决方案(如Sam所建议的)是使用隐式转换来添加pimped方法.这样,您便可以使用所有常用技术来变换Map,但仍然可以在生成的Map上使用额外的方法.我通常使用类而不是特征来执行此操作,因为提供构造函数参数会导致语法更简洁:

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

这使您可以写

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

但是它在collections框架中仍然不能很好地发挥作用.您以地图开始,以Traversable结尾.那不是应该如何运作的.这里的窍门是还使用类型更高级的类型对集合类型进行抽象

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
}

足够简单.您必须提供表示集合的类型Repr和元素的类型T.我使用TraversableLike而不是Traversable,因为它嵌入了它的表示形式.否则,filterFirstTwo将返回Traversable,而不管起始类型是什么.

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.

现在进行隐式转换.在这里,类型符号会变得有些棘手.首先,我使用一种类型较高的类型来捕获集合的表示形式:CC[X] <: Traversable[X],此参数设置CC类型的参数,该类型必须是Traversable的子类(请注意,此处将X用作占位符) ,CC[_] <: Traversable[_]并不意味着同一件事.

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

还有一个隐式的CC[T] <:< TraversableLike[T,CC[T]],编译器使用它隐式地保证我们的集合CC[T]确实是TraversableLike的子类,因此是MoreFilterOperations构造函数的有效参数:

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

到目前为止,太好了.但是仍然存在一个问题...它不适用于地图,因为它们带有两个类型参数.解决方案是使用与以前相同的原理向MoreFilterOperations对象添加另一个隐式对象:

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)

当您还想使用实际上不是集合但可以看成是集合的类型时,真正的美就来了.还记得MoreFilterOperations构造函数中的Repr <% TraversableLike吗?这是一个视图绑定,并允许可以隐式转换为TraversableLike的类型以及直接子类.字符串是一个典型的例子:

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)

如果您现在在REPL上运行它:

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

我还没有尝试使用StreamSetVector进行尝试,但是您可以确信,如果这样做了,它将返回与开始时相同的集合类型

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.

这篇关于在参数化的类中混合通用特征而无需复制类型参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆