I've created a pimp method, collate
, that's usable from any Traversable
or any type that can be coerced to a traversable as per the following example:
val ints = List(0,9,4,5,-3,-5,6,5,-2,1,0,6,-3,-2)
val results = ints collate {
case i: Int if(i < 0) => i.floatValue
} andThen {
case i: Int if(i>5) => i.toString
} andThen {
case i: Int if(i==0) => i
} toTuple
/*
results: (List[Float], List[java.lang.String], List[Int], List[Int]) =
(List(-3.0, -5.0, -2.0, -3.0, -2.0),List(9, 6, 6),List(0, 0),List(4, 5, 5, 1))
*/
Think of it as the unholy spawn of a union 'twixt collect
and partition
, if you will...
It's defined like this:
import collection.generic.CanBuildFrom
class Collatable[Repr <% Traversable[T], T](xs: Repr) {
// Results handling stuff, bit like a poor-man's HList, feel free to skip...
trait Results {
def remainder: Repr
type Append[That] <: Results
def append[That](tup: (That, Repr)): Append[That]
def andThen[R, That](pf: PartialFunction[T, R])
(implicit
matchesBuilder: CanBuildFrom[Repr, R, That],
remainderBuilder: CanBuildFrom[Repr, T, Repr]
) = {
val more = (new Collatable[Repr,T](remainder)).collateOne[R,That](pf)
append(more)
}
}
case class Results9[M1,M2,M3,M4,M5,M6,M7,M8,M9](
m1: M1, m2: M2, m3: M3, m4: M4, m5: M5, m6: M6, m7: M7, m8: M8, m9: M9,
remainder: Repr)
extends Results {
implicit def toTuple = (m1, m2, m3, m4, m5, m6, m7, m8, m9, remainder)
def append[That](tup: (That, Repr)) = error("too many")
}
// ... skip a bit, still in results logic ...
case class Results2[M1,M2](
m1: M1, m2: M2, remainder: Repr)
extends Results {
implicit def toTuple = (m1, m2, remainder)
type Append[That] = Results3[M1,M2,That]
def append[That](tup: (That, Repr)) = Results3(m1, m2, tup._1, tup._2)
}
case class Results1[M1](matches: M1, remainder: Repr) extends Results {
implicit def toTuple = (matches, remainder)
type Append[That] = Results2[M1, That]
def append[That](tup: (That, Repr)) = Results2(matches, tup._1, tup._2)
}
// and now... Our feature presentation!
def collateOne[R, That](pf: PartialFunction[T, R])
(implicit
matchesBuilder: CanBuildFrom[Repr, R, That],
remainderBuilder: CanBuildFrom[Repr, T, Repr]
) = {
val matches = matchesBuilder(xs)
val remainder = remainderBuilder(xs)
for (x <- xs) if (pf.isDefinedAt(x)) matches += pf(x) else remainder += x
(matches.result, remainder.result)
}
def collate[R, That](pf: PartialFunction[T, R])
(implicit
matchesBuilder: CanBuildFrom[Repr, R, That],
remainderBuilder: CanBuildFrom[Repr, T, Repr]
): Results1[That] = {
val tup = collateOne[R,That](pf)
Results1(tup._1, tup._2)
}
}
object Collatable {
def apply[Repr, T](xs: Repr)(implicit witness: Repr => Traversable[T]) = {
new Collatable[Repr, T](xs)
}
}
implicit def traversableIsCollatable[CC[X] <: Traversable[X], A](xs: CC[A]) =
Collatable[CC[A], A](xs)
implicit def stringIsCollatable(xs: String) =
Collatable[String, Char](xs)
Conceptually, it's not all that daunting once you understand how CanBuildFrom
works, but I'm finding that it's it's overwhelmed by boilerplate - especially with the implicits.
I know that I could simplify the ResultX logic a great deal by using an HList, and that's something I probably will do, so that piece of code doesn't especially worry me.
I also know that I could make my life significantly easier if I was able to constrain Repr
as a subtype of Traversable
. But I refuse do that, because then it couldn't be used against Strings. By the same token, I'd also like to avoid forcing the partial functions to return a subtype of T - although this is less of a concern as I could always break my logic into distinct collate and map operations.
More concerning is CanBuildFrom[Repr, T, Repr]
, which I seem to keep repeating, and which obscures the important stuff from my method signatures. This is something I feel sure can be defined just once at the class level, but I haven't yet found a way to make it work.
Any ideas?
Just define types:
class Collatable[Repr <% Traversable[T], T](xs: Repr) {
// Results handling stuff, bit like a poor-man's HList, feel free to skip...
type With[-Elem] = CanBuildFrom[Repr, Elem, Repr]
type CanBuild[-Elem, +To] = CanBuildFrom[Repr, Elem, To]
trait Results {
def remainder: Repr
type Append[That] <: Results
def append[That](tup: (That, Repr)): Append[That]
def andThen[R, That](pf: PartialFunction[T, R])
(implicit
matchesBuilder: CanBuild[R, That],
remainderBuilder: With[T]
) = {
val more = (new Collatable[Repr,T](remainder)).collateOne[R,That](pf)
append(more)
}
}
def collateOne[R, That](pf: PartialFunction[T, R])
(implicit
matchesBuilder: CanBuild[R, That],
remainderBuilder: With[T]
) = {
val matches = matchesBuilder(xs)
val remainder = remainderBuilder(xs)
for (x <- xs) if (pf.isDefinedAt(x)) matches += pf(x) else remainder += x
(matches.result, remainder.result)
}
}
On the other hand, I just noticed that the whole Collatable
is parameterized on Repr
and T
, so why don't you get the implicit remainderBuilder
at that level? Edit Because T
has not been inferred yet. For now, I don't know how to get rid of the extra implicits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With