Thursday, 14 February 2013

breakOut method

I was going through scala collection architecture and found one discussion on breakOut on stackoverflow.com.
Ok, let me attempt to explain this. This method is used as follows-
val myMap: Map[Int, String] = List("India", "United States").map(c => (c.length, c))(breakOut) 
So let's try to understand what's happening in here. Question is, convert given a list of strings (for example list of nations) to Map of [StringLength, String] i.e. [Int, String]

Let's see the definition of map
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 
This method is defined in super trait TraversableLike. All traits prefixed with Like have the actual implementation of collection. So TraversableLike is implementation of Traversable, MapLike is for Map and so on. So map function applies function f to every element of TraversableLike element in our case List[String]. So first parameter f: A=>B decides the type of collection that is going to be returned. You will see how it happens actually.

But before that we need to understand the second param CanBuildFrom[Repr, B, That] of function map which is implicit. CanBuildFrom is a factory class which is implicitly passed with map, which helps to decide what's going to be the return collection Type. Every collection companion object has the defined CanBuildFrom, for example in List companion object has -
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, List[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
If you see List hierarchy, you will find Implicit methods defined in super classes as well, like Seq has implicit and so in Iterable and so on. So when map function is called on List, so appropriate implicit is chosen by scala compiler and used. But if you search there is no implicit available which can give you Map in List hierarchy.  Which means you don't see definition like
implicit def canBuildFrom[A, B]: CanBuildFrom[Coll, (A, B), Map[A, B]]
So if we try to compile following piece of code, it will fail.
val myMap: Map[Int, String] = List("India", "United States").map(c => (c.length, c))

And if we try to see using scala inference the return collection type of
scala> List("India", "United States").map(c => (c.length, c))
res0: List[(Int, String)] = List((5,India), (13,United States))
So clearly we need to either apply one more transformer which convert this List[(Int, String)] to Map[Int, String], or second option is to pass appropriate CanBuildFrom factory as a second argument to map function which will take care of doing rest.
First option has disadvantage of having intermediate collection. Second solution make sense and to achieve that we pass breakOut method. This method returns the appropriate factory for you.

See definition of breakOut:
def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To]
breakOut takes the implicit and returns the required CanBuildFrom. At first look it seems difficult to understand but let's write down all types -

function literal passed to map function f: A => B, gives
A = String
B = (Int, String)

required implicit type by map is CanBuildFrom[Repr, B, That] where
Repr = List[String]
B = (Int, String)
That = Map[Int, String] ---> that's what we need, inferred from val myMap: Map[Int, String]

So breakOut expect us to pass CanBuildFrom[Nothing, (Int, String), Map[Int, String].
CanBuildFrom is contra-variant w.r.t. to first Type parameter, see: CanBuildFrom[-From, -Elem, +To]
and Nothing is bottom class of every class, so we can pass any Type in place of Nothing, and we have such type of CanBuildFrom present.

So breakOut returns proper build factory and pass it to map function which results expected collection type. We don't have any intermediate collection created in this way.

That's it, I hope I was clear.

There are other methods available to do the same things -
List("India", "United States").map(c => (c.length, c)).toMap[Int, String]  //intermediate List
If you don't want intermediate List, we can use view
List("India", "United States").view.map(c => (c.length, c)).toMap[Int, String]
view doesn't apply transformers until it's forced, so you can chain transformers on view and in the end can force to generate required collection.

4 comments:

  1. Thanks. Very helpful.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Nice , very clear . a nice example of explicitly using breakOut can be seen here

    http://stackoverflow.com/a/3249462/1928414

    ReplyDelete
  4. monday night football spread tonight【WG98.VIP】 카지노사이트 카지노사이트 ラッキーニッキー ラッキーニッキー クイーンカジノ クイーンカジノ 876Habanero Hot Sauce - The Best Guide | FBCASINO

    ReplyDelete