Thursday, February 18, 2010

Chaining Partial Functions with orElse

PartialFunctions are extremely valuable Scala constructs that are used in many APIs. Commonly you will encounter the pattern:
  1. obj match {
  2. case "Val" => // do something
  3. case _ => // handle all other cases
  4. }

It is intuitive and obvious how to share the code of the right hand side if the case statement by factoring that code out to a method. But would it not be useful to be able to factor out an entire case statement (PartialFunction) and later chain them together as needed?

This is indeed possible and very easy to do:
  1. /*
  2. We need to declare Partial Functions so to add brevity I am adding this alias
  3. */
  4. scala> import scala.{PartialFunction => PF}
  5. import scala.{PartialFunction=>PF}
  6. /*
  7. You have to explicitly declare the type because the type inferencer cannot know what type of PartialFunction to create
  8.  
  9. A PartialFunction is Strictly type so some functions can only be used on Ints for example
  10. */
  11. scala> val i : PF[Any, Unit] = {case x:Int => println("int found")}
  12. i: PartialFunction[Any,Unit] = < function1>
  13. scala> val j : PF[Any, Unit] =  {case x:Double => println("Double found")}
  14. j: PartialFunction[Any,Unit] = < function1>
  15. scala> val * : PF[Any, Unit] =  {case x=> println("Something else found")}
  16. *: PartialFunction[Any,Unit] = < function1>
  17. /*
  18. one might think that you can do:
  19. 1 match (i orElse j orElse *)
  20. but in fact (i orElse j orElse *) forms a PartialFunction not a pattern so cannot be used with match.  Instead it must be used as a function
  21. */
  22. scala> (i orElse j orElse *)(1)
  23. int found
  24. scala> (i orElse j orElse *)(1.0)
  25. Double found
  26. scala> (i orElse j orElse *)(true)
  27. Something else found
  28. /*
  29. for specific cases it is possible to chain the an anonymous partial function with the common function
  30. This is not so nice so it is probably best to declare a val instead of inline like this
  31. */
  32. scala> (({case s:String => println("String found")}:PF[Any,Unit]) orElse j orElse *)("hello")
  33. String found

For another example of chaining PartialFunctions the Akka tutorial has a good example in the ChatServer trait: http://jonasboner.com/2010/01/04/introducing-akka.html

5 comments:

  1. A nice bit of sugar that was being added to 2.8 is to define a type alias in Predef for PartialFunction
    type =>?[-A, +B] = PartialFunction[A, B]

    then you could say:
    val i : Any =>? Unit = {case x:Int => println("int found")}

    which reads a little nicer I think. I am not sure if they are going to keep it or not.

    for the last example you can also make it a bit terser if you define:

    def =>?[A, B](id : A =>? B) = id


    (=>?[Any, Unit]{case s : String => println("String found")} orElse =>?[Any, Unit]{case i : Int => println("Int found")})("Hi")

    ReplyDelete
  2. good tip. I think that warrants a post

    ReplyDelete
  3. is it just me or is this the most beautiful and elegant thing ever?

    ReplyDelete
  4. And @Ben Jackman's shorthand makes it every cooler!

    ReplyDelete