Friday, November 27, 2009

Defining Custom Control Structures

Scala has only a handful of built-in control structures: for, while, try-catch, if, etc... However it is quite simple to define custom control structures. There are several good examples in the Scala 2.8 library. For some examples look at the classes in the scala.util.control package.

For this topic, we will develop a simplified version of the Exception class. First a demonstration of the 2.8 version.
  1. scala> import scala.util.control.Exception._
  2. import scala.util.control.Exception._
  3. scala> val numberFormat = catching(classOf[NumberFormatException])
  4. numberFormat: util.control.Exception.Catch[Nothing] = Catch(java.lang.NumberFormatException)
  5. scala> val result = numberFormat opt { "10".toInt }               
  6. result: Option[Int] = Some(10)
  7. scala> result match {
  8.      | case Some(n) => // do something
  9.      | case None => // handle this situation
  10.      |


A question that often arises when presented with the Exception control flow is why not use try-catch. There are two main reasons in my opinion.

First, people coming from certain functional style languages find that form more comfortable (so I have heard.) So in this case it is a style issue.

The other reason (and the one more pertinent to Java developers), is that it provides a nice way to handle common exceptions. Why do I say nice? First it declares when exceptions are handled before the code and that provides more context while reading the code. In addition, if you create the Catch object and assign it to a nicely named variable then it is clear what the intent is. For example there may be several reasons to catch a runtime exception and they should be handled differently. A few well-named variables can really assist in readability. A final point is that several of these variables can be defined in on object and shared throughout a project adding some consistency to a project.

Back to the topic at hand.

We will develop a simplified version: a catching method that will return Some(...) if no exception occurs or None if one of the declared exceptions has occurred or throw exception if the exception is not one we declare as handling. Now this is not what I would necessarily call a custom control flow because it does not offer a choice of execution flows. To do that I would add more components like int the Exceptions object. However, this is a good demonstration of the parts required to create a custom control struction. The key is the => in def method(arg: => T) This is a special type construct which means that a no param function is passed in but is not executed until called within the method. A few comparisons:
  1. scala> def method(arg: =>Int) = println(arg)
  2. method: (arg: => Int)Unit
  3. scala> def method2(arg: =>Int) = println("not calling arg")
  4. method2: (arg: => Int)Unit
  5. scala> var x = 1
  6. x: Int = 1
  7. // when method is called x is incremented because method calls the argument
  8. scala> method { x += 1; x }                                
  9. 2
  10. scala> println(x)          
  11. 2
  12. // when method2 is called x is not incremented because 
  13. // the argument is not called/referenced
  14. scala> method2 { x += 1; x }
  15. not calling arg
  16. scala> println(x)           
  17. 2
  18. // arg is referenced 2 times in method three so x is incremented 2 times
  19. scala> def method3(arg: => Int) = println("first call="+arg+" second call="+arg)
  20. method3: (arg: => Int)Unit
  21. scala> method3 { x += 1; x }                                                   
  22. first call=3 second call=4
  23. // Now demonstrate the standard way of defining arguments
  24. // the value passed is calculated before calling the method
  25. // so is at most called once
  26. scala> def byValue(arg: Int) = println(arg)
  27. byValue: (arg: Int)Unit
  28. scala> def byValue2(arg: Int) = println("not calling arg")
  29. byValue2: (arg: Int)Unit
  30. scala> def byValue3(arg: Int) = println("first call="+arg+" second call="+arg)
  31. byValue3: (arg: Int)Unit
  32. scala> byValue{ x += 1; x }
  33. 5
  34. scala> byValue2{ x += 1; x }
  35. not calling arg
  36. scala> println(x)
  37. 6
  38. scala> byValue3{ x += 1; x }
  39. first call=7 second call=7
  40. // And finally show the normal way to pass in a function. 
  41. // This has the benefit of allowing the reader of the code to
  42. // realize that the argument is a function
  43. // but is not a nice syntax for control flow
  44. scala> def stdFunc(arg: ()=>Int) = println(arg())
  45. stdFunc: (arg: () => Int)Unit
  46. scala> def stdFunc2(arg: ()=>Int) = println("not calling arg")
  47. stdFunc2: (arg: () => Int)Unit
  48. scdef stdFunc3(arg: ()=>Int) = println("first call="+arg()+" second call="+arg()) 
  49. stdFunc3: (arg: () => Int)Unit
  50. scala> stdFunc {() => x += 1; x }  
  51. 8
  52. scala> stdFunc2 {() => x += 1; x }
  53. not calling arg
  54. scala> println(x)
  55. 8
  56. scala> stdFunc3 {() => x += 1; x }
  57. first call=9 second call=10


Now for the complete catching example:
  1. scala> def catching[T](exceptions: Class[_]*)(body: => T) = {
  2.      | try {                                                 
  3.      | Some (body)                                           
  4.      | } catch {
  5.      | case e if (exceptions contains e.getClass) => None
  6.      | }                
  7.      | }
  8. catching: [T](exceptions: Class[_]*)(body: => T)Option[T]
  9. scala> val runtime = catching[Number](classOf[NullPointerException], classOf[NumberFormatException])_
  10. runtime: (=> java.lang.Number) => Option[java.lang.Number] = < function1>
  11. scala> runtime { "".toInt }                                                                          
  12. res2: Option[java.lang.Number] = None
  13. scala> runtime { "10".toInt }
  14. res3: Option[java.lang.Number] = Some(10)
  15. scala> runtime { throw new NullPointerException }
  16. res6: Option[java.lang.Number] = None
  17. scala> runtime { throw new RuntimeException }
  18. java.lang.RuntimeException
  19.         at $anonfun$1.apply(< console>:10)
  20.         at $anonfun$1.apply(< console>:10)
  21.         at .catching(< console>:9)
  22.         at $anonfun$1.apply(< console>:8)
  23.         at $anonfun$1.apply(< console>:8)
  24.         at .< init>(< console>:10)
  25.         at .< clinit>(< console>)
  26.         at RequestResult$.< init>(< console>:4)
  27.         at RequestResult$.< clinit>(< console>)
  28.         at RequestResult$result(< console>)
  29.         ...

6 comments:

  1. I wish your blog was around when I started learning Scala. :-)

    ReplyDelete
  2. I'm glad it is here while I learn Scala. Keep up the awesome work!

    ReplyDelete
  3. Thanks Jesse for distilling a daily dose of all those not-so-known Scala features.

    That's very helpful for people like me who don't have time to dig out all the libraries!

    Eric.

    ReplyDelete
  4. Yikes, now I feel like I have to keep putting up intelligent posts :)

    ReplyDelete
  5. Are you planning to cover scala.util.control.Exception in more detail? Or did you already do it and I just didn't find it?

    ReplyDelete
  6. I may cover exception in more detail one day. I do not use that construct myself too often though so it may be some time yet.

    ReplyDelete