Friday, March 14, 2008

Useful Closures in Groovy

Declarative programming is not about how something is done but only what is needed to be done. Declarative programming subtracts housekeeping to make the solution code appear more like the higher level problem statement. A closure can encapsulate some of the low-level requirements in the use of a FileOutputStream so calling code does not have those concerns and, instead, only has to state its intent. First, what is a closure? A closure executes code passed to it. In this example, the block of code is the {println...} in the last line of code below. The block of code passed as an argument is performed when the call() method is called on it. This code prints my name in upper case 3 times.
def tryClosures(code, nbr, text) {
   1.upto(nbr) {
      code.call(text)
   }
}

tryClosures({println(it.toUpperCase())}, 3, "Greg")

That's a nice trick but what is the benefit?

First, a look at the code to store a string of text without the use of closures.
def file = new File("filetouse.txt")
def stream = new FileOutputStream( file );
stream.write( "store this string\n".getBytes() );
stream.close();
Low level code requires housekeeping and provides opportunity for error. The code above has two object instantiations and three method calls - getBytes(), write() and close(). As busy as this code is, it will fail to close the FileOutputStream if an exception is thrown by write(). For something so simple, the code is error prone and complex.

Now, let's see the better alternative. It uses the static use() method in Resource which accepts a closure passed to it. Note that repetitious housekeeping is eliminated from this code, leaving 'DRY', declarative code which more clearly shows intent.
Resource.use { it ->
   it.write("so groovy\n")
}
There, that's all it takes if we create and reuse a component that accepts the closure, i.e., the call to it.write(...). The code for that is below.
class Resource {
   FileOutputStream file;
   Resource() {
      file = new FileOutputStream( new File("sogroovy.txt"), true )
   }

   def static use(closure) {
      def r = new Resource()
      try {
        closure(r)
      }
      finally {
        r.close()
      }
   }
   def void close() {
      file.close()
   }
   def write(String s) {
      file.write(s.getBytes())
   }
}