Scala中最强大的模式之一是enrich-my-library *模式,它使用隐式转换出现来向现有类添加方法,而无需动态方法解析。例如,如果我们希望所有字符串都使用方法spaces
来计算他们拥有的空白字符数,我们可以:
class SpaceCounter(s: String) {
def spaces = s.count(_.isWhitespace)
}
implicit def string_counts_spaces(s: String) = new SpaceCounter(s)
scala> "How many spaces do I have?".spaces
res1: Int = 5
不幸的是,这种模式在处理泛型集合时遇到了麻烦。例如,有人提出了一些关于grouping items sequentially with collections的问题。没有任何内置的功能可以一次性使用,所以这似乎是使用泛型集合C
和泛型元素类型A
来丰富my-library模式的理想候选者:
class SequentiallyGroupingCollection[A, C[A] <: Seq[A]](ca: C[A]) {
def groupIdentical: C[C[A]] = {
if (ca.isEmpty) C.empty[C[A]]
else {
val first = ca.head
val (same,rest) = ca.span(_ == first)
same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
}
}
}
当然,不起作用。 REPL告诉我们:
<console>:12: error: not found: value C
if (ca.isEmpty) C.empty[C[A]]
^
<console>:16: error: type mismatch;
found : Seq[Seq[A]]
required: C[C[A]]
same +: (new SequentiallyGroupingCollection(rest)).groupIdentical
^
有两个问题:我们如何从空C[C[A]]
列表(或从空中)获得C[A]
?我们如何从C[C[A]]
行而不是same +:
返回Seq[Seq[A]]
?
* 以前称为pimp-my-library。
答案 0 :(得分:74)
答案 1 :(得分:29)
从this commit开始,“充实”Scala系列要比Rex给出的出色答案要容易得多。对于简单的情况,它可能看起来像这样,
import scala.collection.generic.{ CanBuildFrom, FromRepr, HasElem }
import language.implicitConversions
class FilterMapImpl[A, Repr](val r : Repr)(implicit hasElem : HasElem[Repr, A]) {
def filterMap[B, That](f : A => Option[B])
(implicit cbf : CanBuildFrom[Repr, B, That]) : That = r.flatMap(f(_).toSeq)
}
implicit def filterMap[Repr : FromRepr](r : Repr) = new FilterMapImpl(r)
将filterMap
操作的“相同结果类型”添加到所有GenTraversableLike
s,
scala> val l = List(1, 2, 3, 4, 5)
l: List[Int] = List(1, 2, 3, 4, 5)
scala> l.filterMap(i => if(i % 2 == 0) Some(i) else None)
res0: List[Int] = List(2, 4)
scala> val a = Array(1, 2, 3, 4, 5)
a: Array[Int] = Array(1, 2, 3, 4, 5)
scala> a.filterMap(i => if(i % 2 == 0) Some(i) else None)
res1: Array[Int] = Array(2, 4)
scala> val s = "Hello World"
s: String = Hello World
scala> s.filterMap(c => if(c >= 'A' && c <= 'Z') Some(c) else None)
res2: String = HW
对于问题中的示例,解决方案现在看起来像,
class GroupIdenticalImpl[A, Repr : FromRepr](val r: Repr)
(implicit hasElem : HasElem[Repr, A]) {
def groupIdentical[That](implicit cbf: CanBuildFrom[Repr,Repr,That]): That = {
val builder = cbf(r)
def group(r: Repr) : Unit = {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if(!rest.isEmpty)
group(rest)
}
if(!r.isEmpty) group(r)
builder.result
}
}
implicit def groupIdentical[Repr : FromRepr](r: Repr) = new GroupIdenticalImpl(r)
示例REPL会话,
scala> val l = List(1, 1, 2, 2, 3, 3, 1, 1)
l: List[Int] = List(1, 1, 2, 2, 3, 3, 1, 1)
scala> l.groupIdentical
res0: List[List[Int]] = List(List(1, 1),List(2, 2),List(3, 3),List(1, 1))
scala> val a = Array(1, 1, 2, 2, 3, 3, 1, 1)
a: Array[Int] = Array(1, 1, 2, 2, 3, 3, 1, 1)
scala> a.groupIdentical
res1: Array[Array[Int]] = Array(Array(1, 1),Array(2, 2),Array(3, 3),Array(1, 1))
scala> val s = "11223311"
s: String = 11223311
scala> s.groupIdentical
res2: scala.collection.immutable.IndexedSeq[String] = Vector(11, 22, 33, 11)
再次注意,观察到的结果类型原则与groupIdentical
上直接定义GenTraversableLike
的方式完全相同。
答案 2 :(得分:9)
截至this commit,魔术咒语与迈尔斯给出的出色答案略有不同。
以下作品,但它是规范的吗?我希望其中一个经典能够纠正它。 (或者说,大炮,大枪之一。)如果视图绑定是上限,则会丢失对Array和String的应用程序。如果绑定是GenTraversableLike或TraversableLike似乎并不重要;但IsTraversableLike为您提供了GenTraversableLike。
import language.implicitConversions
import scala.collection.{ GenTraversable=>GT, GenTraversableLike=>GTL, TraversableLike=>TL }
import scala.collection.generic.{ CanBuildFrom=>CBF, IsTraversableLike=>ITL }
class GroupIdenticalImpl[A, R <% GTL[_,R]](val r: GTL[A,R]) {
def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = {
val builder = cbf(r.repr)
def group(r: GTL[_,R]) {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if (!rest.isEmpty) group(rest)
}
if (!r.isEmpty) group(r)
builder.result
}
}
implicit def groupIdentical[A, R <% GTL[_,R]](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)
不止一种方法可以给有九条生命的猫皮肤。这个版本说,一旦我的源转换为GenTraversableLike,只要我可以从GenTraversable构建结果,就这样做。我对旧的Repr不感兴趣。
class GroupIdenticalImpl[A, R](val r: GTL[A,R]) {
def groupIdentical[That](implicit cbf: CBF[GT[A], GT[A], That]): That = {
val builder = cbf(r.toTraversable)
def group(r: GT[A]) {
val first = r.head
val (same, rest) = r.span(_ == first)
builder += same
if (!rest.isEmpty) group(rest)
}
if (!r.isEmpty) group(r.toTraversable)
builder.result
}
}
implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)
第一次尝试包括将Repr转换为GenTraversableLike的丑陋转换。
import language.implicitConversions
import scala.collection.{ GenTraversableLike }
import scala.collection.generic.{ CanBuildFrom, IsTraversableLike }
type GT[A, B] = GenTraversableLike[A, B]
type CBF[A, B, C] = CanBuildFrom[A, B, C]
type ITL[A] = IsTraversableLike[A]
class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) {
def filterMap[B, That](f: A => Option[B])(implicit cbf : CanBuildFrom[Repr, B, That]): That =
r.flatMap(f(_).toSeq)
}
implicit def filterMap[A, Repr](r: Repr)(implicit fr: ITL[Repr]): FilterMapImpl[fr.A, Repr] =
new FilterMapImpl(fr conversion r)
class GroupIdenticalImpl[A, R](val r: GT[A,R])(implicit fr: ITL[R]) {
def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = {
val builder = cbf(r.repr)
def group(r0: R) {
val r = fr conversion r0
val first = r.head
val (same, other) = r.span(_ == first)
builder += same
val rest = fr conversion other
if (!rest.isEmpty) group(rest.repr)
}
if (!r.isEmpty) group(r.repr)
builder.result
}
}
implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]):
GroupIdenticalImpl[fr.A, R] =
new GroupIdenticalImpl(fr conversion r)