Monday, January 9, 2012

Adding methods to Scala Collections

I don't like getting much involved in these Scala flame wars, but this recent article left me a bit irked. At some point, the following statement is made:

"it is impossible to insert a new method that behaves like a normal collection method. "

So, for the record, that is just not true. Here's an implementation for the filterMap method:

import scala.collection._
import generic.CanBuildFrom
import mutable.Builder
 
class FilterMap[A, C <: Seq[A]](xs: C with SeqLike[A, C]) {
     def filterMap[B, That](f: A => Option[B])
                     (implicit cbf: CanBuildFrom[C, B, That]): That = {
       val builder: Builder[B, That] = cbf()
       xs foreach { x => val y = f(x); if (y.nonEmpty) builder += y.get }
       builder.result()
     }
 }
 
implicit def toFilterMap[A, C <: Seq[A]](xs: C with SeqLike[A, C]): FilterMap[A, C] = new FilterMap[A, C](xs)


Is this what the author wanted? No. He wanted too add a method that behaved like a normal collection method to something that isn't a collection. This is so crazy that, not surprisingly, people are misunderstanding him.


Now, one may come up and say that Scala does do that (add methods to stuff that are not collection), with String and Array. Yes, it does, and it does so by creating a completely new class with all these methods for each of them. Rest assured that if I create a completely new class for each one, I can add filterMap to String and Array too.

6 comments:

  1. Nicely done.

    The author of the original article should spend some time reading through the standard library source.

    ReplyDelete
  2. Er, except this doesn't address the issue at all. This method doesn't work on Arrays, Strings, etc., but that's OK, since apparently that goal is crazy.

    Although I'm interested in all feedback, I'm saddened by this response, esp. coming from dcsobral. It epitomizes precisely the type of stubborn dismissal from the community that I was writing about.

    ReplyDelete
  3. @Yang Zhang, I don't see dismissal from the community. My twitter feed indicates that generally the community thinks your post in well articulated and thoughtful.

    In fact, Martin has acknowledged on hacker news, that some steps may have to be taken (such as a compiler flag).

    Daniel's blog entry addresses one of your statement - which it's not dismissal but a retort on a very specific point.

    You may feel saddened, but I don't think you need to. That's part of having a dialog through the "internets".

    ReplyDelete
  4. @Yang Yes, it doesn't work on Arrays, Strings, etc, because it is targeted at Seqs.

    What I don't understand is why you think it is reasonable to want a single method to serve completely different classes. You can solve the problem by writing wrappers for String and Array (like the standard library does -- two for each!), you can solve the problem by being explicit in the type at the call site, yet neither alternative satisfy you.

    Who's being stubborn here?

    ReplyDelete
  5. Maybe using <% instead of <: will help?

    ReplyDelete
  6. "Crazy" or not, you can actually do it in most cases as long as you're willing to write more than one implicit conversion. (Usually you don't need extra classes, just the right CanBuildFrom.)

    ReplyDelete