I often find myself needing to chain collects where I want to do multiple collects in a single traversal. I also would like to return a "remainder" for things that don't match any of the collects.
For example:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String, age: Int) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11))
// Normal way
val cats: List[Cat] = animals.collect { case c: Cat => c }
val dogAges: List[Int] = animals.collect { case Dog(_, age) => age }
val rem: List[Animal] = Nil // No easy way to create this without repeated code
This really isn't great, it requires multiple iterations and there is no reasonable way to calculate the remainder. I could write a very complicated fold to pull this off, but it would be really nasty.
Instead, I usually opt for mutation which is fairly similar to the logic you would have in a fold:
import scala.collection.mutable.ListBuffer
// Ugly, hide the mutation away
val (cats2, dogsAges2, rem2) = {
// Lose some benefits of type inference
val cs = ListBuffer[Cat]()
val da = ListBuffer[Int]()
val rem = ListBuffer[Animal]()
// Bad separation of concerns, I have to merge all of my functions
animals.foreach {
case c: Cat => cs += c
case Dog(_, age) => da += age
case other => rem += other
}
(cs.toList, da.toList, rem.toList)
}
I don't like this one bit, it has worse type inference and separation of concerns since I have to merge all of the various partial functions. It also requires lots of lines of code.
What I want, are some useful patterns, like a collect that returns the remainder (I grant that partitionMap new in 2.13 does this, but uglier). I also could use some form of pipe or map for operating on parts of tuples. Here are some made up utilities:
implicit class ListSyntax[A](xs: List[A]) {
import scala.collection.mutable.ListBuffer
// Collect and return remainder
// A specialized form of new 2.13 partitionMap
def collectR[B](pf: PartialFunction[A, B]): (List[B], List[A]) = {
val rem = new ListBuffer[A]()
val res = new ListBuffer[B]()
val f = pf.lift
for (elt <- xs) {
f(elt) match {
case Some(r) => res += r
case None => rem += elt
}
}
(res.toList, rem.toList)
}
}
implicit class Tuple2Syntax[A, B](x: Tuple2[A, B]){
def chainR[C](f: B => C): Tuple2[A, C] = x.copy(_2 = f(x._2))
}
Now, I can write this in a way that could be done in a single traversal (with a lazy datastructure) and yet follows functional, immutable practice:
// Relatively pretty, can imagine lazy forms using a single iteration
val (cats3, (dogAges3, rem3)) =
animals.collectR { case c: Cat => c }
.chainR(_.collectR { case Dog(_, age) => age })
My question is, are there patterns like this? It smells like the type of thing that would be in a library like Cats, FS2, or ZIO, but I am not sure what it might be called.
Scastie link of code examples: https://scastie.scala-lang.org/Egz78fnGR6KyqlUTNTv9DQ
I wanted to see just how "nasty" a fold() would be.
val (cats
,dogAges
,rem) = animals.foldRight((List.empty[Cat]
,List.empty[Int]
,List.empty[Animal])) {
case (c:Cat, (cs,ds,rs)) => (c::cs, ds, rs)
case (Dog(_,d),(cs,ds,rs)) => (cs, d::ds, rs)
case (r, (cs,ds,rs)) => (cs, ds, r::rs)
}
Eye of the beholder I suppose.
How about defining a couple utility classes to help you with this?
case class ListCollect[A](list: List[A]) {
def partialCollect[B](f: PartialFunction[A, B]): ChainCollect[List[B], A] = {
val (cs, rem) = list.partition(f.isDefinedAt)
new ChainCollect((cs.map(f), rem))
}
}
case class ChainCollect[A, B](tuple: (A, List[B])) {
def partialCollect[C](f: PartialFunction[B, C]): ChainCollect[(A, List[C]), B] = {
val (cs, rem) = tuple._2.partition(f.isDefinedAt)
ChainCollect(((tuple._1, cs.map(f)), rem))
}
}
ListCollect is just meant to start the chain, and ChainCollect takes the previous remainder (the second element of the tuple) and tries to apply a PartialFunction to it, creating a new ChainCollect object. I'm not particularly fond of the nested tuples this produces, but you may be able to make it look a bit better if you use Shapeless's HLists.
val ((cats, dogs), rem) = ListCollect(animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.tuple
Scastie
Dotty's *: type makes this a bit easier:
opaque type ChainResult[Prev <: Tuple, Rem] = (Prev, List[Rem])
extension [P <: Tuple, R, N](chainRes: ChainResult[P, R]) {
def partialCollect(f: PartialFunction[R, N]): ChainResult[List[N] *: P, R] = {
val (cs, rem) = chainRes._2.partition(f.isDefinedAt)
(cs.map(f) *: chainRes._1, rem)
}
}
This does end up in the output being reversed, but it doesn't have that ugly nesting from my previous approach:
val ((owls, dogs, cats), rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
/* more animals */
case class Owl(wisdom: Double) extends Animal
case class Fly(isAnimal: Boolean) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11), Owl(200), Fly(false))
Scastie
And if you still don't like that, you can always define a few more helper methods to reverse the tuple, add the extension on a List without requiring an EmptyTuple to begin with, etc.
//Add this to the ChainResult extension
def end: Reverse[List[R] *: P] = {
def revHelp[A <: Tuple, R <: Tuple](acc: A, rest: R): RevHelp[A, R] =
rest match {
case EmptyTuple => acc.asInstanceOf[RevHelp[A, R]]
case h *: t => revHelp(h *: acc, t).asInstanceOf[RevHelp[A, R]]
}
revHelp(EmptyTuple, chainRes._2 *: chainRes._1)
}
//Helpful types for safety
type Reverse[T <: Tuple] = RevHelp[EmptyTuple, T]
type RevHelp[A <: Tuple, R <: Tuple] <: Tuple = R match {
case EmptyTuple => A
case h *: t => RevHelp[h *: A, t]
}
And now you can do this:
val (cats, dogs, owls, rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
.end
Scastie
Since you mentioned cats, I would also add solution using foldMap:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
case class Snake(name: String) extends Animal
val animals: List[Animal] = List(Cat("Bob"), Dog("Spot"), Cat("Sally"), Dog("Jim"), Snake("Billy"))
val map = animals.foldMap{ //Map(other -> List(Snake(Billy)), cats -> List(Cat(Bob), Cat(Sally)), dogs -> List(Dog(Spot), Dog(Jim)))
case d: Dog => Map("dogs" -> List(d))
case c: Cat => Map("cats" -> List(c))
case o => Map("other" -> List(o))
}
val tuples = animals.foldMap{ //(List(Dog(Spot), Dog(Jim)),List(Cat(Bob), Cat(Sally)),List(Snake(Billy)))
case d: Dog => (List(d), Nil, Nil)
case c: Cat => (Nil, List(c), Nil)
case o => (Nil, Nil, List(o))
}
Arguably it's more succinct than fold version, but it has to combine partial results using monoids, so it won't be as performant.
This code is dividing a list into three sets, so the natural way to do this is to use partition twice:
val (cats, notCat) = animals.partitionMap{
case c: Cat => Left(c)
case x => Right(x)
}
val (dogAges, rem) = notCat.partitionMap {
case Dog(_, age) => Left(age)
case x => Right(x)
}
A helper method can simplify this
def partitionCollect[T, U](list: List[T])(pf: PartialFunction[T, U]): (List[U], List[T]) =
list.partitionMap {
case t if pf.isDefinedAt(t) => Left(pf(t))
case x => Right(x)
}
val (cats, notCat) = partitionCollect(animals) { case c: Cat => c }
val (dogAges, rem) = partitionCollect(notCat) { case Dog(_, age) => age }
This is clearly extensible to more categories, with the slight irritation of having to invent temporary variable names (which could be overcome by explicit n-way partition methods)
I have a list which may contain three different types of class, and all extends from class E, such as A extends E, B extends E and C extends E. And I need to identify each element in the list and do some calculations accordingly. (list may contain a little more subclass of E in the future.)
I prefer to use map or partition or groupBy rather than just if, but i get more confused right now. As I am very new to Scala, if anyone can share some idea? Thank you!
val list = //some codes to get the list//
list.groupby{
_.getClass //so in this line, is it possible to call the calculation method accordingly?
}
trait A extends E {
def calA = {...}
}
trait B {
def calB = {...}
}
trait C {
def calC = {...}
}
You can use pattern matching handle the different classes:
val list = List(1, "s", "t")
list map {
case a: A => a.calA
case b: B => b.calB
case i: Int => i + 5
case s: String => s.toUpperCase
}
// -> List(6, "S", "T")
list groupBy {
case a: E => "E" // grouping A, B and C together
case i: Int => "Int"
case s: String => "String"
}
// -> Map("Int" -> List(1), "String" -> List("s", "t"))
I noticed the repeating pattern in my code, and thought this may be a good idea to give structural typing a try. I've read the chapter ;-), but I can't quite get my head around it. Consider the following code:
def link(user: User, group: Group) = {
UserGroupLinks.insert((user.id, group.id))
}
def link(group: Group, role: Role) = {
GroupRoleLinks.insert((group.id, role.id))
}
def link(user: User, role: Role) = {
UserRoleLinks.insert((user.id, role.id))
}
How do I combine it into something like:
def link(A <: ...something with an id, B<:... something with and id) = {
(A, B) match {
case (u: User, g: Group) => UserGroupLinks.insert((user.id, group.id))
case (g: Group, r: Role) => GroupRoleLinks.insert((group.id, role.id))
case (u: User, r: Role) => UserRoleLinks.insert((user.id, role.id))
case _ =>
}
}
With structural types it would be something like this:
type WithId = {def id:Int}
def link(a:WithId, b:WithId) =
(a, b) match {
case (u:User, g:Group) => UserGroupLinks.insert(u.id -> g.id)
case _ =>
}
Edit
You could go a bit further and let the compiler help you with selecting the correct inserter. For that you would need to introduce a trait on your inserters:
trait WithInsert[A, B] {
def insert(x: (Int, Int)): Unit
}
And then on your insert objects do this:
object UserGroupLinks extends WithInsert[User, Group]
You can define the default ones on the companion object
object WithInsert {
implicit val ug = UserGroupLinks
implicit val gr = GroupRoleLinks
}
We can still use the WithId type although in most cases I would recommend using a trait
type WithId = { def id: Int }
Your link method then would look like this:
def link[A <: WithId, B <: WithId](a: A, b: B)(implicit inserter: WithInsert[A, B]) =
inserter.insert(a.id -> b.id)
As you can see the link method expects a WithInsert[A, B] to be available. It will find the appropriate one in the companion object of WithInsert.
That means you can now call your method simply like this:
link(user, group)
link(group, role)
This is the simple solution I was looking for:
def link[A <: { def id: Long }, B <: { def id: Long }](a: A, b: B) = {
(a, b) match {
case (u: User, g: Group) => UserGroupLinks.insert((u.id, g.id))
case (g: Group, r: Role) => GroupRoleLinks.insert((g.id, r.id))
case (u: User, r: Role) => UserRoleLinks.insert((u.id, r.id))
case _ => ???
}
}
Where the compiler would allow:
case class XX(id: Long)
case class YY(id: Long)
case class ZZ(idz: Long)
link(XX(22), YY(33))
But not:
link(XX(22), ZZ(33))
Try
type T = { val id: String }
def link(obj1 : T, obj2: T) = {
// You got
println(" obj1.id = %s obj2.id = %s".format(obj1.id, obj2.id))
}
I have a class structure like this
abstract class A
class B extends A
class C extends A
class D extends A
class E extends A
and I have a collection of the various instances, for example:
val xs = List(new D, new B, new E, new E, new C, new B)
My question is, is there an elegant way to filter out some of the subclasses from the List?
Let's say I want all instances except B's and C's. I can do it with a bunch of isInstanceOf's, or using collect like this:
val ys = (xs collect {
case b: B => None
case c: C => None
case notBorC => notBorC
}).filter(_ != None).asInstanceOf[List[A]]
This works but it feels awkward, mostly because of the filter and cast. Is there a more elegant way? Less code is preferred, and I'd like to have a solution that doesn't need to be updated if I add more subclasses of A.
collect can be used to filter values on which the function is defined:
Get all values of type A:
xs.collect { case a: A => a }
Get all values except B and C:
xs diff xs.collect { case x#(_: B | _: C) => x }
flatMap that shit! (as they say):
scala> val ys = xs flatMap {
| case _: B | _: C => None
| case other => Some(other)
| }
ys: List[A] = List(D#7ecdc97b, E#2ce07e6b, E#468bb9d1)
In your case you were getting a List[ScalaObject] because ScalaObject is the least upper bound of None, D, and E.
Formulating problems as questions seems to be a pretty good way of solving them :) My question actually provides the answer - just filter by subtype:
val ys = xs filterNot(List(classOf[B], classOf[C]) contains _.getClass)
I have the following class hierarchy:
class A
class B extends A
class C extends A
then, there is another class which takes instances of these classes and there is a method, in which two cases of pattern-matching are possible like this:
class D (one: A, two: A) {
def work {
(one, two) match {
case (o, t): (B, B) => ... blablabla
case (o, t): (B, C) => ... blablabla
case _ =>
}
}
}
However, when it should resolve the matching in favor of the second case (B, C), it tries resolving it as (B, B) and comes up with the class cast exception that C cannot be cast to B. Why? What to do? How can I come around this?
Your syntax isn't quite right (doesn't compile).
This works though:
object Matcher extends App {
class A
class B extends A
class C extends A
class D(one: A, two: A) {
def work {
(one, two) match {
case (o: B, t: B) => println("B")
case (o: B, t: C) => println("C")
case _ =>
}
}
}
val d1 = new D(new B, new B)
val d2 = new D(new B, new C)
d1.work
//B
d2.work
//C
}
The problem, as always, is erased types. (B,C) is syntactic sugar for Tuple2[B,C], which is erased to Tuple2 at runtime. The case statement verifies that (B,C) matches Tuple2, but then fails to cast it.
In your case, the easiest solution would be to match against 'one' and 'two' individually, rather than wrapping them in a tuple:
one match {
case o : B => two match {
case p : C => ...
case p : B => ...
}
...
}
It's not so pretty, but it won't suffer from the same problems.
Edit: Actually, I'd go with Brian Smith's solution - matching inside the tuple rather than outside. It avoids the problem in a similar way, but looks nicer.
I made this code work.
Firstly I added a case to your class definition.
case class A
case class B extends A
case class C extends A
Secondly I changed the work.
class D(one: A, two: A) {
def work {
(one, two) match {
case (o: B, t: B) => println("BB")
case (o: B, t: C) => println("BC")
case (o: C, t: C) => println("CC")
case _ => println("AA")
}
}
}
Now what I got:
new D(B(),B()).work => BB
new D(B(),C()).work => BC
new D(C(),C()).work => CC
new D(A(),B()).work => AA
The case adds an apply and an unapply method.