Wednesday, October 28, 2009

Extractors 3 (Operator Style Matching)

One of the blessings and curses of Scala the several rules for creating expressive code (curse because it can be used for evil.) One such rule is related to extractors that allows the following style of match pattern:
  1. List(1,2,3) match {
  2.   case 1 :: _ => println("found a list that starts with a 1")
  3.   case _ => println("boo")
  4. }

The rule is very simple. An extractor object that returns Option[Tuple2[_,_]] (or equivalently Option[(_,_)]) can be expressed in this form.

In other words: object X {defunapply(in:String):Option[(String,String)] = ...} can be used in a case statement like: case first X head => ... or case"a" X head => ....

Example to extract out the vowels from a string:
  1. scala>object X { defunapply(in:String):Option[(RichString,RichString)] = Some(in.partition( "aeiou" contains _ )) }
  2. defined module X
  3. scala>"hello world"match { case head X tail => println(head, tail) }
  4. (eoo,hll wrld)
  5. // This is equivalent but a different way of expressing it
  6. scala>"hello world"match { case X(head, tail) => println(head, tail) }       
  7. (eoo,hll wrld)


Example for Matching the last element in a list. Thanks to 3-things-you-didnt-know-scala-pattern.html:
  1. scala>object ::> {defunapply[A] (l: List[A]) = Some( (l.init, l.last) )}
  2. defined module $colon$colon$greater
  3. scala> List(1, 2, 3) match {
  4.      | case _ ::> last => println(last)
  5.      | }
  6. 3
  7. scala> (1 to 9).toList match {
  8.      | case List(1, 2, 3, 4, 5, 6, 7, 8) ::> 9 => "woah!"
  9.      | }
  10. res12: java.lang.String = woah!
  11. scala> (1 to 9).toList match {
  12.      | case List(1, 2, 3, 4, 5, 6, 7) ::> 8 ::> 9 => "w00t!"
  13.      | }
  14. res13: java.lang.String = w00t!

6 comments:

  1. Awesome, this definitely helps me to understand this type of pattern matching, thanks!

    ReplyDelete
  2. Note that my last-list-element extractor does not cover all cases- I've made it shorter for the purpose of the example. In order to be complete, you need to match empty lists:

    object ::> {def unapply[A] (l: List[A]) = l match {
    case Nil => None
    case _ => Some( (l.init, l.last) )
    }
    }

    ReplyDelete
  3. @Vassil. You are correct my examples do not cover all the possible cases. If you are using this example in real-life you need to add the rest of the cases to make the match block complete.

    ReplyDelete
  4. Hi there, awesome site. I thought the topics you posted on were very interesting. I tried to add your RSS to my feed reader

    and it a few. take a look at it, hopefully I can add you and follow.

    Extractors

    ReplyDelete
  5. Thanks for sharing your ideas and thoughts, i like your blog and bookmark this blog for further use thanks again…


    Extractors

    ReplyDelete
  6. As a newcomer to the scala lang I've found these daily posts a great resource for learning. Thanks for them!

    Just minor note to the last example, I suppose that if you use an operator form "head X tail" the name of X plays role in precedence (rules for expressions apply) and that's why in

    case List(1, 2, 3, 4, 5, 6, 7) ::> 8 ::> 9

    leftmost ::> takes precedence (because the name ends with >), whereas in

    case 1 :: 2 :: List(3, 4, 5, 6, 7, 8)

    rightmost :: takes precedence (because the name ends with :)



    ReplyDelete