【发布时间】:2013-04-17 19:38:34
【问题描述】:
如何在 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 语言中已经存在的功能。
我问过couplequestions 了解任务的各个方面,但我认为这涵盖了整个问题。到目前为止,我得到的最好的response 是使用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 my library 模式。
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)
}
这样,您只需实例化 Sets 而不是 CustomSets 并根据需要进行隐式转换以取平均值。
scala> (Set(1,2,3) & Set(2,3,5)).mean
res4: Float = 2.0
这满足了我最初的愿望清单的大部分内容,但仍然未能通过第 (5) 项。
axel22 在下面的 cmets 中所说的话正是我问这个问题的核心。
至于继承,不可变(集合)类不容易 一般继承...
这符合我的经验,但从语言设计的角度来看,这里似乎有些问题。 Scala 是一种面向对象的语言。 (当我去年看到 Martin Odersky 发表演讲时,这是他强调的 Haskell 的卖点。)不变性是明确首选的操作模式。 Scala 的集合类被吹捧为其对象库的皇冠上的明珠。然而,当你想扩展一个不可变的集合类时,你会遇到所有这些非正式的传说,大意是“不要那样做”或“除非你真的知道你在做什么,否则不要尝试它。正在做。”通常类的目的是使它们易于扩展。 (毕竟,集合类没有标记为final。)我正在尝试确定这是 Scala 中的设计缺陷还是我没有看到的设计权衡。
【问题讨论】:
标签: scala inheritance set builder