扩展Scala集合的简单示例 [英] Simple example of extending a Scala collection
问题描述
我正在寻找一个非常简单的子类化Scala集合的例子。我对这一切是如何以及为何有效的全面解释并不感兴趣;有很多可用的此处和其他地方在互联网上。我想知道这样做的简单方法。
I'm looking for a very simple example of subclassing a Scala collection. I'm not so much interested in full explanations of how and why it all works; plenty of those are available here and elsewhere on the Internet. I'd like to know the simple way to do it.
下面的类可能是一个尽可能简单的例子。这个想法是,创建一个 Set [Int]
的子类,它有一个额外的方法:
The class below might be as simple an example as possible. The idea is, make a subclass of Set[Int]
which has one additional method:
class SlightlyCustomizedSet extends Set[Int] {
def findOdd: Option[Int] = find(_ % 2 == 1)
}
显然这是错误的。一个问题是没有构造函数将东西放入 Set
。必须构建 CanBuildFrom
对象,最好通过调用一些已知的库代码来知道如何构建它。我已经看到在配对对象中实现几个附加方法的示例,但它们显示了它是如何工作的或如何做更复杂的事情。我想看看如何利用库中已有的东西来解决这几行代码问题。实现这个的最小,最简单的方法是什么?
Obviously this is wrong. One problem is that there's no constructor to put things into the Set
. A CanBuildFrom
object must be built, preferably by calling some already-existing library code that knows how to build it. I've seen examples that implement several additional methods in the companion object, but they're showing how it all works or how to do something more complicated. I'd like to see how to leverage what's already in the libraries to knock this out in a couple lines of code. What's the smallest, simplest way to implement this?
推荐答案
如果你只想向一个类添加一个方法,那么子类化可能不是要走的路。 Scala的集合库有点复杂,并且叶子类并不总是适合子类化(可以从子类化 HashSet
开始,但是这会让你开始深入了解兔子孔)。
If you just want to add a single method to a class, then subclassing may not be the way to go. Scala's collections library is somewhat complicated, and leaf classes aren't always amenable to subclassing (one might start by subclassing HashSet
, but this would start you on a journey down a deep rabbit hole).
实现目标的一种更简单的方法可能是:
Perhaps a simpler way to achieve your goal would be something like:
implicit class SetPimper(val s: Set[Int]) extends AnyVal {
def findOdd: Option[Int] = s.find(_ % 2 == 1)
}
这实际上并不是Set的子类,而是创建一个隐式转换,允许你做以下事情:
This doesn't actually subclass Set, but creates an implicit conversion that allows you to do things like:
Set(1,2,3).findOdd // Some(1)
打破兔子洞
如果你来自Java背景,那可能会令人惊讶扩展标准集合是如此困难 - 在所有Java标准库充满 juArrayList
子类之后,几乎任何可以包含其他内容的东西hings。但是,Scala有一个关键区别:它的首选集合都是不可变的。
Down the Rabbit Hole
If you've come from a Java background, it might be surprising that it's so difficult to extend standard collections - after all the Java standard library's peppered with j.u.ArrayList
subclasses, for pretty much anything that can contain other things. However, Scala has one key difference: its first-choice collections are all immutable.
这意味着它们没有 add
就地修改它们的方法。相反,他们有 +
方法构建一个新实例,包含所有原始项目以及新项目。如果他们天真地实现这一点,那就非常低效,因此他们使用各种特定于类的技巧来允许新实例与原始实例共享数据。 +
方法甚至可以返回与原始对象不同的对象 - 一些集合类对小集合或空集合使用不同的表示。
This means that they don't have add
methods that modify them in-place. Instead, they have +
methods that construct a new instance, with all the original items, plus the new item. If they'd implemented this naïvely, it'd be very inefficient, so they use various class-specific tricks to allow the new instances to share data with the original one. The +
method may even return an object of a different type to the original - some of the collections classes use a different representation for small or empty collections.
但是,这也意味着如果你想要继承其中一个不可变集合,那么你需要理解你正在子类化的类的内容,以确保你的子类的实例是以与基类相同的方式构造。
However, this also means that if you want to subclass one of the immutable collections, then you need to understand the guts of the class you're subclassing, to ensure that your instances of your subclass are constructed in the same way as the base class.
顺便说一句,如果你想要对可变集合进行子类化,这些都不适用于你。他们被视为scala世界中的二等公民,但他们做有添加
方法,并且很少需要构建新实例。以下代码:
By the way, none of this applies to you if you want to subclass the mutable collections. They're seen as second class citizens in the scala world, but they do have add
methods, and rarely need to construct new instances. The following code:
class ListOfUsers(users: Int*) extends scala.collection.mutable.HashSet[Int] {
this ++= users
def findOdd: Option[Int] = find(_ % 2 == 1)
}
在大多数情况下,你可能会做出或多或少的预期( map
,朋友可能不会做你所期待的,因为 CanBuildFrom
我会在一分钟内得到的东西,但请耐心等待。)
Will probably do more-or-less what you expect in most cases (map
and friends might not do quite what you expect, because of the the CanBuildFrom
stuff that I'll get to in a minute, but bear with me).
如果继承失败了,我们总是有一个核选项可以依靠:组成。我们可以创建自己的 Set
子类,将其职责委托给委托,如下所示:
If inheritance fails us, we always have a nuclear option to fall back on: composition. We can create our own Set
subclass that delegates its responsibilities to a delegate, as such:
import scala.collection.SetLike
import scala.collection.mutable.Builder
import scala.collection.generic.CanBuildFrom
class UserSet(delegate: Set[Int]) extends Set[Int] with SetLike[Int, UserSet] {
override def contains(key: Int) = delegate.contains(key)
override def iterator = delegate.iterator
override def +(elem: Int) = new UserSet(delegate + elem)
override def -(elem: Int) = new UserSet(delegate - elem)
override def empty = new UserSet(Set.empty)
override def newBuilder = UserSet.newBuilder
override def foreach[U](f: Int => U) = delegate.foreach(f) // Optional
override def size = delegate.size // Optional
}
object UserSet {
def apply(users: Int*) = (newBuilder ++= users).result()
def newBuilder = new Builder[Int, UserSet] {
private var delegateBuilder = Set.newBuilder[Int]
override def +=(elem: Int) = {
delegateBuilder += elem
this
}
override def clear() = delegateBuilder.clear()
override def result() = new UserSet(delegateBuilder.result())
}
implicit object UserSetCanBuildFrom extends CanBuildFrom[UserSet, Int, UserSet] {
override def apply() = newBuilder
override def apply(from: UserSet) = newBuilder
}
}
这可以说太复杂,太简单了。它的代码行数比我们写的要多得多,然而,它仍然非常天真。
This is arguably both too complicated and too simple at the same time. It's far more lines of code than we meant to write, and yet, it's still pretty naïve.
没有伴侣类它会工作,但没有 CanBuildFrom
, map
将返回一个简单的 Set
,这可能不是你的期望。我们还重写了 Set
的文档建议我们实现的可选方法。
It'll work without the companion class, but without CanBuildFrom
, map
will return a plain Set
, which may not be what you expect. We've also overridden the optional methods that the documentation for Set
recommends we implement.
如果我们是彻底的,我们已经为我们的可变类创建了一个 CanBuildFrom
,并实现了 empty
,因为这样可以确保少数几个创建新实例的方法将按预期工作。
If we were being thorough, we'd have created a CanBuildFrom
, and implemented empty
for our mutable class, as this ensures that the handful of methods that create new instances will work as we expect.
如果这听起来太多了,请考虑以下内容:
If that sounds like too much work, consider something like the following:
case class UserSet(users: Set[Int])
当然,你必须输入几个字母来获取用户集,但是我认为它比子类更好地区分问题。
Sure, you have to type a few more letters to get at the set of users, but I think it separates concerns better than subclassing.
这篇关于扩展Scala集合的简单示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!