如何在 Scala 中编写自定义整数集?具体来说,我想要一个具有以下属性的类:
- 它是不可变的。
- 它扩展了
Set
特征。 - 所有收集操作都将根据需要返回此类型的另一个对象。
- 它在其构造函数中采用整数参数的变量列表。
- 它的字符串表示形式是用大括号括起来的以逗号分隔的元素列表。
- 它定义了一个
mean
返回元素平均值的方法。
例如:
CustomIntSet(1,2,3) & CustomIntSet(2,3,4) // returns CustomIntSet(2, 3)
CustomIntSet(1,2,3).toString // returns {1, 2, 3}
CustomIntSet(2,3).mean // returns 2.5
(1) 和 (2) 确保该对象以正确的 Scala 方式执行操作。(3) 要求正确编写builder代码。(4)确保构造函数可以自定义。(5) 是如何覆盖现有toString
实现的示例。(6) 是如何添加新功能的示例。
这应该使用最少的源代码和样板来完成,尽可能利用 Scala 语言中已经存在的功能。
我已经就任务的各个方面提出了几个 问题,但我认为这个问题涵盖了整个问题。到目前为止,我得到的最佳响应SetProxy
是使用,这很有帮助,但在上面的 (3) 中失败了。我已经广泛研究了《Scala编程》第二版中的“Scala 集合的体系结构”一章,并查阅了各种在线示例,但仍然一头雾水。
我这样做的目的是写一篇博文,比较 Scala 和 Java 处理这个问题的方式的设计权衡,但在我这样做之前,我必须实际编写 Scala 代码。我不认为这会那么困难,但确实如此,而且我承认失败。
经过几天的折腾,我想出了以下解决方案。
package example
import scala.collection.{SetLike, mutable}
import scala.collection.immutable.HashSet
import scala.collection.generic.CanBuildFrom
case class CustomSet(self: Set[Int] = new HashSet[Int].empty) extends Set[Int] with SetLike[Int, CustomSet] {
lazy val mean: Float = sum / size
override def toString() = mkString("{", ",", "}")
protected[this] override def newBuilder = CustomSet.newBuilder
override def empty = CustomSet.empty
def contains(elem: Int) = self.contains(elem)
def +(elem: Int) = CustomSet(self + elem)
def -(elem: Int) = CustomSet(self - elem)
def iterator = self.iterator
}
object CustomSet {
def apply(values: Int*): CustomSet = new CustomSet ++ values
def empty = new CustomSet
def newBuilder: mutable.Builder[Int, CustomSet] = new mutable.SetBuilder[Int, CustomSet](empty)
implicit def canBuildFrom: CanBuildFrom[CustomSet, Int, CustomSet] = new CanBuildFrom[CustomSet, Int, CustomSet] {
def apply(from: CustomSet) = newBuilder
def apply() = newBuilder
}
def main(args: Array[String]) {
val s = CustomSet(2, 3, 5, 7) & CustomSet(5, 7, 11, 13)
println(s + " has mean " + s.mean)
}
}
这似乎符合上述所有标准,但它有很多样板。我发现以下 Java 版本更容易理解。
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
public class CustomSet extends HashSet<Integer> {
public CustomSet(Integer... elements) {
Collections.addAll(this, elements);
}
public float mean() {
int s = 0;
for (int i : this)
s += i;
return (float) s / size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Iterator<Integer> i = iterator(); i.hasNext(); ) {
sb.append(i.next());
if (i.hasNext())
sb.append(", ");
}
return "{" + sb + "}";
}
public static void main(String[] args) {
CustomSet s1 = new CustomSet(2, 3, 5, 7, 11);
CustomSet s2 = new CustomSet(5, 7, 11, 13, 17);
s1.retainAll(s2);
System.out.println("The intersection " + s1 + " has mean " + s1.mean());
}
}
这很糟糕,因为 Scala 的卖点之一是它比 Java 更简洁明了。
Scala 版本中有很多不透明的代码。SetLike
, newBuilder
, 和canBuildFrom
都是语言样板:它们与用花括号编写集合或取平均值无关。我几乎可以接受它们作为您为 Scala 的不可变集合类库支付的价格(目前接受不可变作为一种不合格的商品),但是仍然留下contains
, +
, -
, 并且iterator
它们只是样板传递代码。它们至少和 getter 和 setter 函数一样丑陋。
似乎 Scala 应该提供一种不编写Set
接口样板的方法,但我想不通。我尝试使用SetProxy
和扩展具体HashSet
类而不是抽象类,Set
但这两者都给出了令人困惑的编译器错误。
有没有办法在没有contains
, +
,-
和iterator
定义的情况下编写这段代码,或者上面是我能做的最好的吗?
按照下面axel22的建议,我编写了一个简单的实现,它利用了非常有用的如果不幸命名为 pimp 我的库模式。
package example
class CustomSet(s: Set[Int]) {
lazy val mean: Float = s.sum / s.size
}
object CustomSet {
implicit def setToCustomSet(s: Set[Int]) = new CustomSet(s)
}
有了这个,您只需实例化Set
s 而不是CustomSet
s 并根据需要进行隐式转换以获得平均值。
scala> (Set(1,2,3) & Set(2,3,5)).mean
res4: Float = 2.0
这满足了我最初的愿望清单中的大部分内容,但仍然未能通过第 (5) 项。
axel22在下面的评论中所说的话是我问这个问题的核心。
至于继承,不可变(集合)类一般都不容易继承……</p>
这符合我的经验,但从语言设计的角度来看,这里似乎有些不对劲。Scala 是一种面向对象的语言。(当我去年看到 Martin Odersky 发表演讲时,这是他强调的 Haskell 的卖点。)不变性是明确首选的操作模式。Scala 的集合类被吹捧为其对象库的皇冠上的明珠。然而,当你想扩展一个不可变的集合类时,你会遇到所有这些非正式的传说,大意是“不要那样做”或“除非你真的知道自己在做什么,否则不要尝试”。通常类的目的是使它们易于扩展。(毕竟,集合类没有标记final
。)我'