4

我试图做的是提出一个案例类,我可以在模式匹配中使用它,它只有一个字段,例如一个不可变集。此外,我想利用 map、foldLeft 等应该传递给集合的函数。我尝试如下:

case class foo(s:Set[String]) extends Iterable[String] {
    override def iterator = s.iterator
}

现在,如果我尝试使用例如 map 函数,我会收到一个类型错误:

var bar = foo(Set() + "test1" + "test2")
bar = bar.map(x => x)

 found   : Iterable[String]
 required: foo
   bar = bar.map(x => x)
                ^

类型错误非常好(在我的理解中)。但是,我想知道如何为一个集合实现一个包装案例类,以便可以调用 map、foldLeft 等并且仍然接收案例类的对象。需要覆盖所有这些功能还是有其他方法?

编辑

我倾向于接受对我有用的 Régis Jean-Gilles 的解决方案。然而,在谷歌上搜索了几个小时后,我发现了另一个有趣的 Scala 特征,名为SetProxy. 我找不到任何琐碎的例子,所以我不确定这个特征是否符合我的要求:

  1. 想出一个自定义类型,即不同的类型Set
  2. 类型必须是案例类(我们要做模式匹配)
  3. 我们需要“委托”方法 map、foldLeft 等,它们应该将调用传递给我们的实际集合,并以我们的新类型返回结果集合包裹的 arround

我的第一个想法是扩展Set,但我的自定义类型Foo已经扩展了另一个类。因此,第二个想法是混合特征 Iterable 和 IterableLike。SetProxy现在我对让我思考哪个是“最好”的方式的特质感到愤怒。你有什么想法和经历?

自从我三天前开始学习 Scala 以来,任何指针都非常感谢!

4

2 回答 2

5

嗯,这对我来说听起来很有希望,但 Scala 说变量 b 是 Iterable[String] 类型而不是 Foo 类型,即我看不出 IterableLike 在这种情况下有何帮助

你说的对。仅从 mpartel 所示的继承IterableLike将使某些方法的返回类型更精确(例如filter, 它将返回 Foo),但对于像您这样的其他方法mapflatMap则需要提供适当的CanBuildFrom隐式。这是一个执行此操作的代码片段:

import collection.IterableLike
import collection.generic.CanBuildFrom
import collection.mutable.Builder

case class Foo( s:Set[String] ) extends Iterable[String] with IterableLike[String, Foo] {
  override def iterator = s.iterator
  override protected[this] def newBuilder: scala.collection.mutable.Builder[String, Foo] = new Foo.FooBuilder
  def +(elem: String ): Foo = new Foo( s + elem )
}

object Foo  {
  val empty: Foo = Foo( Set.empty[String] )
  def apply( elems: String* ) = new Foo( elems.toSet )

  class FooBuilder extends Builder[String, Foo] {
    protected var elems: Foo = empty
    def +=(x: String): this.type = { elems = elems + x; this }
    def clear() { elems = empty }
    def result: Foo = elems
  }

  implicit def canBuildFrom[T]: CanBuildFrom[Foo, String, Foo] = new CanBuildFrom[Foo, String, Foo] {
    def apply(from: Foo) = apply()
    def apply() = new FooBuilder
  }
}

在 repl 中进行了一些测试:

scala> var bar = Foo(Set() + "test1" + "test2")
bar: Foo = (test1, test2)

scala> bar = bar.map(x => x) // compiles just fine because map now returns Foo
bar: Foo = (test1, test2)
于 2013-05-12T11:45:36.293 回答
1

继承IterableLike[String, Foo]为您提供了所有这些方法,以便它们返回 Foo。IterableLike要求您实施newBuilderiterator.

import scala.collection.IterableLike
import scala.collection.mutable.{Builder, SetBuilder}

case class Foo(stuff: Set[String]) extends Iterable[String] with IterableLike[String, Foo] {
  def iterator: Iterator[String] = stuff.iterator

  protected[this] override def newBuilder: Builder[String, Foo] = {
    new SetBuilder[String, Set[String]](Set.empty).mapResult(Foo(_))
  }
}

// Test:
val a = Foo(Set("a", "b", "c"))
val b = a.map(_.toUpperCase)
println(b.toList.sorted.mkString(", "))  // Prints A, B, C
于 2013-05-12T08:54:06.730 回答