Sunday, August 8, 2010

Delimited Continuations

Scala's delimited continuations, introduced in version 2.8, can be used to implement all sorts of interesting control constructs.

This is a very long blog post. It took me quite a while to get my head around Scala's reset and shift operators. To help others hopefully avoid the stumbling blocks I encountered, I have tried here to start with the basics and build up from there in some detail. If you want a shorter explanation, see the Resources section at the end of this post for pointers to some other blog entries that are more succinct.

Contents

Mechanics

In order to use Scala's delimited continuations, you must use version 2.8, and you must use the continuations (or CPS) compiler plugin. You do this by specifying a command line option when running both the compiler and the runtime:

$ scalac -P:continuations:enable ${sourcefiles}
$ scala -P:continuations:enable ${classname}
In your source code, you must import the appropriate continuations elements, which you can do most simply by using a wildcard to import everything:
import scala.util.continuations._
If you forget to do the import you will get an error message similar to this:
<console>:6: error: not found: value reset
       reset {
       ^

Continuation Passing Style (CPS)

In order to understand how Scala's delimited continuations work, you have to understand the "continuation passing style", or CPS.

Consider this code in which a method makes a subroutine call:
def main {
    pre
    sub()
    post
}
def sub() {
    substuff
}
where pre and post represent all of the code in main respectively before and after the call to sub, and substuff represents all of the code in sub.

When the sub method gets called, the system, in effect, instructs the processor to execute the sub code, then to continue execution within main immediately after the call to sub.

We can conceptually refactor the code in main so that all of the stuff in pre is in a separate method, and all of the post stuff is in a separate method. We can further refactor the code so that each section (pre, sub, post) takes in all of its input data as arguments and passes all of its data changes out as an aggregate return value (such as a Map or Tuple) of the method for that section. Adding arguments and return value to main, we have something that looks like this:
def main(m:M):Z = {
    val x:X = pre(m)
    val y:Y = sub(m,x)
    val z:Z = post(m,x,y)
    return z
}
def sub(m:M,x:X):Y {
    val y:Y = substuff(m,x)
    return y
}
Now, instead of the system automatically continuing execution at post after finishing sub, let's make that explicit in our code by passing the chunk of code that calls post as an extra argument to sub. We will then modify sub so that, after doing all of its calculations and generating the values it would have returned to main as y, it instead calls post with its arguments as specified, and returns as its own value the return value of post, which is z in main.
def main(m:M) {
    val x:X = pre(m)
    val z:Z = sub(m,x, { post(m,x,_) } )
    return z
}
def sub(m:M,x:X, subCont: (Y) => Z) {
    val y:Y = substuff(m,x)
    val z:Z = subCont(y)
    return z
}
When we pass the code fragment containing post to sub, Scala generates a closure that captures the values available to post at that point, including m and x, so that when that closure is evaluated later it can get those values.

Note that the main method no longer sees y, the original return value from sub, so it can't be explicitly passed to post; instead, we use a placeholder, which is filled in by the code in sub that calls post. We can rewrite that line to use the more explicit function syntax (where, for convenience, we use y as our parameter name):
    val z:Z = sub(m,x, { (y:Y) => post(m,x,y) } )
The gist of CPS is that we don't use return. Rather than calling a subroutine and having it return to us, as is the case in the normal Direct Style, we pass a continuation to the subroutine for it to execute when it is done.

Nested CPS

In the above example we have only taken the first step in converting to CPS. To be able to take advantage of CPS, we need to complete the transformation.

At the top, our main method is still returning a value. Since we have no return in CPS, how do we handle this? The answer is that the topmost level can not return a value. Let's add a top-level wrapper like this:
def prog(m:M) {
    val z:Z = main(m)
    println(z)
    System.exit(z.exitValue)
}
Now we can make the same CPS transformation on prog and main as we did before on main and sub:
def prog(m:M) {
    main(m, { (z:Z) =>
        println(z)
        System.exit(z.exitValue)
    })
}

def main(m:M, mainCont:(Z)=>Unit):Unit = {
    val x:X = pre(m)
    val z:Z = sub(m,x, { (y:Y) => post(m,x,y) } )
    mainCont(z)
}
We are still using a return statement in sub, with code in main following the return from sub. To fix this, we need to push the mainCont in main into the continuation we pass to sub. We modify both main and sub to do this:
def main(m:M, mainCont:(Z)=>Unit):Unit = {
    val x:X = pre(m)
    sub(m,x, { (y:Y) => {
        val z:Z = post(m,x,y) } )
        mainCont(z)
    })
}

def sub(m:M,x:X, subCont: (Y) => Unit) {
    val y:Y = substuff(m,x)
    subCont(y)
}
We have now threaded our top-level continuation - the one that includes the call to System.exit - all the way down to sub, so when we execute the subCont in sub, it will first execute the post method with the code in main that originally appeared after sub, then it will execute the code in prog that originally appeared after the call to main, which will call println and then exit the program by calling System.exit.

If we wanted to convert substuff to CPS, we would apply the same transformation to it and sub, after which the call from sub to substuff would pass an additional argument which was the continuation of the rest of sub, which includes the continuation passed from main to sub, which in turn includes the continuation passed from prog into main.

As you can see, each continuation that we pass down to another subroutine always includes the continuations for all of the callers. In other words, every continuation includes all of the rest of the program to be executed after the called subroutine is done. The other important point is that in every method where we call a subroutine using CPS, that call is always the very last thing in the method.

Full versus Delimited Continuations

In the discussion above we have assumed that the entire program is converted over to CPS. This is the classical definition of continuations, which can be referred to as full continuations. However, using CPS in languages (such as Scala) that were not specifically designed for it can be awkward, so it would be nicer if we could restrict the use of CPS to the specific areas in our code where we want to use it.

This is exactly the intent of a delimited continuation. Rather than attempting to capture the entire remainder of the program execution in a continuation, we only capture the remaining execution of the program up to a specified point.

If we reexamine the start of our sample program, the prog method, we see that the only difference between it and any arbitrary method is that we can't return a Direct Style value from it. If we remove the call to System.exit, we can call prog from normal Direct Style code, with CPS being used within prog and all of its converted subroutines. Program execution within the CPS code proceeds normally using CPS, each method ending by passing a continuation along to the next method. After the last continuation is finally executed, the CPS code is done and control returns to the caller of prog.
def prog(m:M) {
    main(m, { (z:Z) =>
        println(z)
    })
}

Uses

We have gone to a lot of trouble to restructure our code to use CPS while keeping the functionality the same. Now we can examine how we can make changes to the code that are only possible because it uses CPS.

The key ability that CPS gives us is that we have an explicit object (the continuation) representing the remainder of execution of our program (or, in the case of a delimited continuation, of a portion of our program). In the code sample above, we executed that continuation once we reached the end of the line in sub. But what would happen if, instead of executing the continuation at that point, we just saved it somewhere, such as into a singleton?
object ContinuationSaver {
    var savedContinuation:Option[()=>Unit] = None
    def save(saveCont: =>Unit) = savedContinuation = Some(saveCont _)
}
def sub(m:M,x:X, subCont: (Y) => Unit) {
    val y:Y = substuff(m,x)
    ContinuationSaver.save { subCont(y) }
}
After sub saves the continuation, it is done, and in fact the entire delimited continuation is done; control returns to the caller of prog. But in ContinuationSaver we still have the continuation that represents execution of the remainder of that portion of the program, which we can execute later. In effect, we have placed the execution of that code into suspended animation, to be revived at some later time of our choosing.

Not only can we call the continuation later, we can call it multiple times. We can also write a more sophisticated ContinuationSaver that can save multiple continuations and keep track of which ones we should execute later, including the order and whether to call them multiple times. We can even save the continuations to persistent storage or move them to another computer, as is done by Swarm.

CPS With Return

In pure CPS, there are no returns. But code in Scala does return, even when we are using CPS. In the previous section I used the phrase "control returns to the caller of prog." This happens in the normal way, by having each of the intervening methods return to its caller until the stack unwinds to the first CPS call. I have assumed that each CPS method returns no value (Unit), but there is nothing preventing us from adding code to each method in the transformed CPS chain to make it return a value.

The examples above demonstrate a transformation from Direct Style code to CPS code, and that transformation always results in code that returns Unit. If we add a return value to the transformed code, this is not something we can get as a result of using the above transformation technique.

What happens if we add a return value to our CPS code? In our examples above, the execution of the continuation was always the last thing in the subroutine. If we keep this as our default behavior, then when we change the CPS methods to return a value, the return value from the last CPS method in a chain of continuations will propagate back up through the chain of CPS callers all the way out to the topmost CPS method, and will appear to the Direct Style code as the value of that outermost method. Of course, one of the intervening CPS method might modify or replace that value as it is being returned through it.

For example, let's take the most recent version of sub above (the one that saves the continuation for later execution) and make it return an Int value:
object ContinuationSaver {
    var numberOfSavedContinuations = 0
    var savedContinuation:Option[()=>Unit] = None
    def save(saveCont: =>Unit):Int = {
        savedContinuation = saveCont _
        numberOfSavedContinuations = numberOfSavedContinuations + 1
        numberOfSavedContinuations
    }
}
def sub(m:M,x:X, subCont: (Y) => Unit):Int = {
    val y:Y = substuff(m,x)
    ContinuationSaver.save { subCont(y) }
}
We also change the rest of the methods in our calling chain to allow us to propagate this value all the way out. Since the call to sub is the last call in main, all we need to do is change the return type on main to match the return type of sub. Likewise, since the call to main is the last call in prog, we change the return type of prog to match the return type of main:
def prog(m:M):Int = {
    main(m, { (z:Z) =>
        println(z)
    })
}

def main(m:M, mainCont:(Z)=>Unit):Int = {
    val x:X = pre(m)
    sub(m,x, { (y:Y) => {
        val z:Z = post(m,x,y) } )
        mainCont(z)
    })
}
We could, if we wanted to, modify main to make a change to the value returned by sub before passing it back as its own return value, or we could make main return something else entirely.

If you think about the CPS code as having been created by transforming some Direct Style code, you can see that the untransformed code had its original return type, and the now-CPS transformed code has a (potentially different) transformed return type.

Reset and Shift

Finally, we have enough background to understand Scala's reset and shift keywords.

The Scala implementation of delimited continuations was created by Tiark Rompf of EPFL, and is described in his explanatory paper on Delimited Continuations in Scala with co-authors Ingo Maier and Martin Odersky. There are also some quotes below from some of Tiark's posts.

Reset is the keyword that demarcates the limits of the delimited continuation. Within the body of the reset, the code is CPS code; the return value of reset is not CPS.

Shift is the keyword that indicates the bottoming out of the CPS path. The body of the shift is not CPS code, but it's untransformed return value is CPS. The shift call gets passed as its argument the continuation that has been collected from all of the callers out to the (dynamically) enclosing reset.

Reset and shift are thus the keywords that take you from Direct Style to CPS, and from CPS to Direct Style, respectively. All of the code between reset and shift is CPS. Any method that includes shift must be marked as CPS, and any method that calls a CPS method must be marked as CPS, until you reach the enclosing reset call.

When you use reset and shift in your code, the continuations compiler plugin transforms your code in a manner similar to the CPS transformation I described above. All of the code from the end of the shift block to the end of the enclosing method or reset block is packaged up as a closure and passed to the body of the shift block as the continuation function.

Let's break down some examples of reset and shift in Scala.
reset {
  shift { k: (Int=>Int) =>
    k(7)
  } + 1
}
The shift statement tells the compiler plugin to restructure the code as in our CPS examples, by converting the code after the shift call into a continuation that gets passed as an argument to the shift. To make it easier to see what that means in this case, let's do that code transformation in a few steps.

First, we assign the result of the shift call to a variable and use that variable later in the code:
reset {
  var r = shift { k: (Int=>Int) =>
    k(7)
  }
  r + 1
}
Second, we convert all of the code following the shift into a function and call it:
reset {
  var r = shift { k: (Int=>Int) =>
    k(7)
  }
  def f(x:Int) = x + 1
  f(r)
}
The function f is our continuation function that represents all of the code between the end of the shift block and the end of the enclosing reset block. Finally, we transform the code as is done by the compiler plugin, binding our continuation function f(x) to the shift parameter k, and making the return value of the fully transformed code be the return value of the body of the shift:
reset {
  def f(x:Int) = x + 1
  f(7)
}
Now we can easily see that the return value is 8.

We can apply the same transformations to
reset {
  shift { k: (Int=>Int) =>
    k(k(k(7)))
  } + 1
}
to get
reset {
  def f(x:Int) = x + 1
  f(f(f(7)))
}
from which we can quickly calculate that this will return a value of 10.

All of our transformations have no effect on anything outside of the reset; for example,
reset {
  shift { k: (Int=>Int) =>
    k(7)
  } + 1
} * 2
just multiplies the return value of the reset expression by 2, so the result of this code snippet would be 16.

Tiark's paper gives this interesting example:
reset {
  shift { k: (Int=>Int) =>
    k(k(k(7))); "done"
  } + 1
}
and points out that the value of this code snippet is "done". The continuation function k is called three times, but the value of that expression is discarded. If we apply our code transformations as before, we see that this transforms into:
reset {
  def f(x:Int) = x + 1
  f(f(f(7))); "done"
}
which makes it more obvious why the result of this code snippet is "done".

A key detail to note here is that the value of the evaluated reset block is not the value of the last expression in that block, as it is in most code. Instead, the value of the evaluated reset block is the value of the last expression in the shift block that gets executed within that reset block. Execution of the body of the shift is always the last thing that happens within the enclosing reset block.

When you look at a shift block and see its return value being used in an expression, as in the "shift + 1" examples above, remember that, due to code transformation, that "return" from the shift block never actually happens as a return. Instead, once execution reaches the shift block, the code after that block gets passed to it as a continuation; if the code in the shift block calls the continuation, the value which is passed as an argument to the continuation appears as the value being returned from the shift block. Thus the type of the argument passed to the shift block's continuation function is the same as the type of the return value of the shift in the source code, and the type of the return value of that continuation function is the same as the type of the return value of the original last value in the reset block that encloses the shift block.

There are thus three types associated with shift:
  • The type of the argument to pass to the continuation, which is the same as the syntactic return type of the shift in the source code.
  • The type of the return from the continuation, which is the same as the return type of all of the code that follows the shift block in the source code (i.e. the type of the last value in the block of code between the shift block and the end of the function or reset block containing the shift block). This is called the untransformed return type.
  • The type of the last value in the shift block, which becomes the type of the return value of the enclosing function or return block. This is called the transformed return type.
In the signature for shift, the above three types appear as A, B and C, respectively:
def shift[A, B, C](fun: ((A) => B) => C): A @scala.util.continuations.cpsParam[B,C]
The two types in the cpsParam annotation always represent the untransformed and the transformed return types, respectively. The CPS annotations are described in more detail below.

The signature for reset only uses two types: the first type is the untransformed type of the code block passed to reset, which matches the B type of shift, and the second type is the type of the transformed code block, which matches the C type of shift, and is also the real return type of the reset block to its caller. The scaladoc for reset uses parameter type names A and C, but I write it here using B and C so that the signature of the ctx by-name parameter matches the signature of the return value of shift:
def reset[B, C](ctx: => B @scala.util.continuations.cpsParam[B,C]): C   
Here's where those types appear:
C = reset { ...; A = shift { k:(A=>B) => ...; C } ...; B }  
In the following example, A=Int, B=String and C=Boolean:
def is123(n:Int):Boolean = {
  reset {
    shift { k : (Int=>String) =>
      (k(n) == "123")
    }.toString
  }
}

Annotations

As you saw above, the signatures for reset and shift include the cpsParam annotation. The compiler plugin uses this type annotation to select what pieces of code to transform to CPS; in Tiark's paper this is referred to as a "type-directed selective CPS transform." If you just use reset and shift without any subroutine calls, you may never need to explicitly use a CPS annotation. But if you put any shift calls into subroutines, as described below, then you will need to use a CPS annotation.

The base annotation is cpsParam[-B, +C]. This annotation tells the compiler that the corresponding block of code has an untransformed return value of type B and a transformed return value of type C, as described in the discussion of the types for reset and shift above.

To simplify the annotation for the common case where the transformed return type is the same as the untransformed type, the continuations package defines the convenience type cps:
type cps[A] = cpsParam[A, A]
If you are looking at old posts on the web, be aware that the cpsParam annotation used to be called simply cps; the old cps annotation was renamed to cpsParam and the new one-type-parameter cps type alias was added.

In the Uses section above we discussed the possibility of saving away the continuation for later execution, after which control returns to the caller. If we do this, we can't return a value from the suspended code to the original caller, since that code has not been executed yet, and the eventual executor of the continuation may not know where it came from, so it too is likely not to care about a return value.

In order to simplify the source code for this typical case, the Scala continuations library includes a special annotation type, suspendable:
type suspendable = cpsParam[Unit, Unit]
In addition to being more succinct, this annotation type can be used to make it clear that this function may suspend its continuation so that it can finish execution later.

Nested Shift

In all of the above examples, the shift block appears directly inside the reset block, and the cpsParam type of the reset block must match the cpsParam type of the shift block.

What happens if you put the shift block in a separate function and call that function from the reset block? In this case, the function containing the shift block must be marked as a CPS function by using the cpsParam annotation on its return type, and that cpsParam type must be the same as the cpsParam type of the enclosed shift block. When this function is invoked from within the reset block, the compiler plugin knows how to transform that block such that the code after the call to the CPS function becomes part of a continuation which is passed in to the CPS function, just as in the Nested CPS examples above.
def is123(n:Int):Boolean = {
  reset {
    is123sub(n)
  }
}

def is123sub(n:Int):String @cpsParam[String,Boolean] = {
    shift { k : (Int=>String) =>
      (k(n) == "123")
    }.toString
}
The function containing the shift block can be refactored to push that shift block down into another function, in which case that new function must also have the same signature as the original function and the shift block. Thus the entire chain of functions between the reset and the shift are all tied together with the same CPS signature.

What if you have an existing CPS function, but you want to call it and change its return type? If you were to follow the pattern of regular code, you might start by trying something like this in order to return floating point 1 or 0 rather than the Boolean true or false returned by a reset block that just calls is123sub.
//this won't compile
def is123f(n:Int):Float = {
    reset {
        val x = is123sub(n)
        if (x) 1.0 else 0.0
    }
}
This does not work as expected; the line of code following the call to is123sub is not operating on what will be the return value of the reset block, despite it being the last statement in that block. Instead, due to the code transformation described above that is being done by the CPS compiler plugin, code added after the call to is123sub gets bundled up as part of the continuation passed to the shift block within is123sub. The code that follows the call to the CPS function must end with a type that matches the first parameter of the cpsParam part of the signature of the function; in this case, String The untransformed return type of is123sub is also String, so in this case the block of code that follows the call to is123sub must take a String (as the return value of the call to is123sub) and must also return a String (which becomes the return value of the shift block within is123sub).

If we want to intercept the Boolean value that is being calculated in the shift block within is123sub, we must do that from within another shift block. The body of a shift block is written in Direct Style, and our subroutine is123sub is CPS, so we can't call it from within the new shift block. What we have to do is to put the new shift block before the call to is123sub. The call to is123sub then becomes part of the continuation that is passed to the new shift block, and we can add code within the new shift block that receives the transformed result of the shift block in is123sub and converts it as desired.

To see the control flow a little more clearly, you can execute this code snippet:
reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}
Here's the output the above code produces:
A
B
D
E
G
F
C
You can see from the order of execution that the second shift block is being executed as part of the continuation that is passed to the first shift block. Despite the fact that one appears before the other in the source code, the two shift blocks are actually nested. The compiler plugin notices this and handles them slightly differently to prevent the nested shift block from escaping from the enclosing reset block.

To show how all of the types thread together, here is a little piece of code with explicit type annotations on the reset and shift blocks in which you can see sets of places for which the same type needs to be used. The assert statements help show how the values are getting passed around.
def nestedShifts[T1,T2,T3,T4,T5](t1:T1,t2:T2,t3:T3,t4:T4,t5:T5):T2 = {
    reset[T1,T2] {
        val s1:T3 = shift[T3,T5,T2] { k1: (T3=>T5) =>
            val r1:T5 = k1(t3)
            assert(r1==t5)
            t2  //this is the return value of nestedShifts
        }
        assert(s1==t3)
        val s2:T4 = shift[T4,T1,T5] { k2: (T4=>T1) =>
            val r2:T1 = k2(t4)
            assert(r2==t1)
            t5
        }
        assert(s2==t4)
        t1
    }
}
If you get a compiler error when nesting CPS functions like this, try modifying the code to assign the value of the nested CPS function to a local variable, then end with that variable:
def is123f(n:Int):Float = {
    reset {
        val x = shift { k:(Int=>Boolean) =>
            if (k(n)) 1.0f else 0.0f
        }
        val r = is123sub(x)
        r
    }
}
If you leave out the val r and just end the reset block with the call to is123sub, you will get an error such as this:
<console>:13: error: type mismatch;
 found   : String @scala.util.continuations.cpsParam[String,Boolean]
 required: String @scala.util.continuations.cpsParam[String,Float]
           is123sub(x)
                   ^

Control Construct Restrictions

Because of the code transformation performed by the continuations compiler plugin, there are some control constructs that can not be used when calling a CPS function.

Using return statements in a CPS function is unlikely to do what you expect, and may cause type mismatch compiler errors, so you should not use them.

When using an if statement, you may get an error like this:
Foo.scala:21: error: then and else parts must both be cps code or neither of them
Tiark's advice is not to use explicit return, and maybe use shiftUnit on the non-CPS value.

The compiler plugin does not handle try blocks, so you can't catch exceptions within CPS code. Those exceptions will be propagated out to the enclosing reset block and can be caught there - unless the continuation is suspended and executed later, in which case any exceptions would be propagated to the reset block of the code doing that later execution.

You need to be careful when using looping constructs. As Tiark says,
Capturing delimited continuations inside a while loop turns the loop basically into a general recursive function.
You can follow the above link for details, but basically each invocation of shift within a looping construct allocates another stack frame, so after "looping" many times you will likely get a StackOverflowError.

Some looping constructs can not be used with a shift inside them. To quote Tiark again:
In a reset block you can do anything, but shifts are not allowed everywhere. The limitation is that everything on the call path between a shift and its enclosing reset must be "shift-aware". That rules out the regular foreach, map and filter methods because they know nothing about continuations, so they can't call closures containing shift.

Advice

As I mentioned at the start of this post, it took me some time to feel that I had a good understanding of how reset and shift work. You may not get it in one reading of this post. As with any new coding concept, the best way to gain a working understanding is to try using it in some of your own code. You will need patience; the CPS error messages are not always clear.

If you are interested in playing with control constructs, such as actors or generators, then you should definitely take the time to understand reset and shift. You might also want to take a look at Swarm.

On the other hand, you may never need to deal with reset and shift. Now that they are available in Scala, I expect some people will create libraries that build on reset and shift to present APIs for developers that are simpler to understand. Still, even when using those simpler APIs you may find that an understanding of the content of this post will be useful.

Resources

Updated 2010-08-09 to fix error pointed out by mgm7734.
Updated 2010-09-26 to fix error pointed out by Nikolay.

Friday, May 28, 2010

My Misperception of Scala's Ordered Trait

A brief tale of a little misperception that I had, and how it was corrected.

The Ordered trait in Scala is typically used when defining a class of objects that know how to order themselves by comparing against other instances of that class. The typical class definition looks something like this:

case class Thing(val n:Int) extends Ordered[Thing] {
    def compare(that: Thing): Int = { this.n - that.n }  //[1]
}
When I first started looking at these definitions, they just looked wrong to me: it seemed like Ordered[Thing] depended on the definition of Thing, and Thing in turn was defined in terms of Ordered[Thing]; a circular definition!

It is of course not a circular definition. The appropriate interpretation was made obvious to me recently while reviewing some Scala code with a co-worker, who was using the Ordered trait in a non-canonical way. Instead of the usual use of Ordered as in the above class definition, his class definition looked similar to this:
case class Thing(val n:Int) extends Ordered[Any] {
    def compare(that: Any): Int = that match {
        case i:Int => this.n - i
        case x:Thing => this.n - x.n
        case _ => throw new IllegalArgumentException("bad type")
    }
}
The reason he did this was because he wanted to be able to compare a Thing - which has an Int value as part of its definition - against either a Thing or an Int. The smallest common superclass of Thing and Int is Any, so his compare method had to accept an argument of type Any, which in turn meant the Ordered trait must be Ordered[Any].

This somewhat unusual use of Ordered led us to another small problem. He had an Array[Thing], sorted according to Thing.compare, on which he wanted to do a binary search. We couldn't find a binary search built in to Scala, but it was simple enough to find one on the web. We grabbed the Scala implementation of binary search from RosettaCode.org (and changed the type of the argument from IndexedSeq[A] to Array[A] since we were using Scala 2.7):
def binarySearch[A <% Ordered[A]](a: Array[A], v: A) = {
  def recurse(low: Int, high: Int): Option[Int] = (low + high) / 2 match {
    case _ if high < low => None
    case mid if a(mid) > v => recurse(low, mid - 1)
    case mid if a(mid) < v => recurse(mid + 1, high)
    case mid => Some(mid)
  }
  recurse(0, a.size - 1)
}
Our code to call the binarySearch method looked something like this:
val a:Array[Thing] = Array(Thing(1), Thing(3), Thing(5), Thing(6))
val x = binarySearch(a,3)
It failed to compile, with an error like this REPL output:
<console>:7: error: type mismatch;
 found   : Array[Thing]
 required: Array[Any]
       val x = binarySearch(a,3)
                            ^
<console>:7: error: no implicit argument matching parameter type (Any) => Ordered[Any] was found.
       val x = binarySearch(a,3)
               ^
The cause of this pair of error messages may be obvious to some people, but we scratched our heads a while trying to figure out what we had done wrong. Eventually we realized that the binarySearch method was making the assumption that the element type of the array was same as the type of the sort; in other words, the binarySearch method only worked on arrays of elements where each element, of type A, implemented Ordered[A]. Since our Thing class did not do that, we were getting a type mismatch when trying to pass it to binarySearch.

The solution was to modify binarySearch to explicitly have two type parameters, one for the element type of the array (A) and one for the ordering type of those elements (B), i.e. the type of the values against which we can compare the array elements:
def binarySearch[A,B](a: Array[A with Ordered[B]], v: B) = {
  def recurse(low: Int, high: Int): Option[Int] = (low + high) / 2 match {
    case _ if high < low => None
    case mid if a(mid) > v => recurse(low, mid - 1)
    case mid if a(mid) < v => recurse(mid + 1, high)
    case mid => Some(mid)
  }
  recurse(0, a.size - 1)
}
Going through this exercise helped me clearly see the distinction between the element type and the ordering type, and more generally to firmly excise the mistaken perception of circular dependency I mentioned at the start of this post. Now when I see class Foo extends Bar[Foo], it's easy for me to remember that just means that class Foo implements methods, as declared in Bar, that happen to take arguments of type Foo [2].

Footnotes

[1] Using a straight subtraction like this for the comparison will fail in extreme cases, such as if the first value is MAX_VALUE and the second value is negative; however, in the interest of keeping the code example simple, I have used this very short and understandable code snippet.

[2] Yes, I know this is not a very precise statement: Bar[Foo] might only define variables rather than methods, or the methods might take arguments of type List[Foo] rather than Foo, or any of a number of other variations.

Updated 2010-07-10 to escape angle brackets as pointed out by tolund.

Sunday, January 10, 2010

Reload That Config File

It is common for applications to load a configuration file on startup to control various options. Some applications can also reload their configuration file while running, allowing you to modify the application configuration without having to restart the application.

Contents

Goals

Configuration files (or config files) are useful because they let us change the behavior of an application with a mechanism that is much simpler and faster than modifying the application source and recompiling it. Being able to reload the configuration of a running application allows us to take that concept a bit further, as generally we can make reloading the configuration operationally simpler and faster than shutting down and restarting the application.

When reloading the configuration, we have the following goals:
  • Reloading a configuration should be a simple operation for the operator to trigger.
  • It should not be possible to load an invalid configuration. If the operator tries to do so, the application should continue running with the old configuration.
  • When reloading a configuration, the application should smoothly switch from the old configuration to the new configuration, ensuring that it is always operating with a consistent configuration. More precisely, an operational sequence that requires a consistent set of configuration parameters for the entire sequence should complete its sequence with the same set of configuration parameters as were active when the sequence started.
  • The application should provide feedback so that the operator knows what the application is doing. Logging, notification or statistics about configuration reloads should be available.

Dependency Injection

Dependency injection (DI) is a form of structural configuration in which different suppliers of a service are wired into an application based on the contents of a configuration file. In the typical case, these configurations are unlikely to change once an application has started. Although in principle it is possible to reload a DI configuration, and thus all of the discussion below could apply, in practice you might want to separate out the kind of relatively static structural configuration that is typically done with DI from the more dynamic parametric configuration that you might want to change while the application is running, and use different mechanisms to implement those two sets of configurations.

Alternatively, you can selectively disallow (as part of your validation step) configuration changes that are too much work to implement, requiring the user who wants to make such changes to restart the application.

Config Contents

If you think of a config file as being a set of late-binding commands for controlling the behavior of an application program, it should be clear that the most flexible config file is one that is itself a program. Applications that already have a built-in interpreter, such as emacs and applications written in Lisp, often simply feed their config files to their interpreter, giving them the full power of a Turing-complete language in which to express site-specific program behavior.

If you have an interpreter available, this can be a reasonable option: it is simple to implement, takes little work to document (assuming you already have to provide documentation for the interpreted language anyway), and provides a great deal of flexibility. One potential downside is that you might not want all of the power of the language to be available in a config file; in particular, if you are using the language internally, your program may have made available certain functions that you don't want a user to call from a config file. If your application already has a security framework built in to it, this may be easy enough to do, or you may not be concerned about it. In any case, you should at least be aware of this potential pitfall if you choose to use a language as your config file syntax.

At the other end of the spectrum, you could choose a standard name=value format, such as Windows INI file or a Java Properties file If you have a relatively simple application with just a few config parameters to set, this is probably a reasonable option.

You can treat all of your config data as strings and let the application deal with each individually, or you can define a set of datatypes that can be uniformly represented in a config file. This might include lists of data or other compound types.

One of the typical capabilities implemented in config systems is the ability to group config parameters into logical groupings. The standard Windows INI file does this with its [section] prefixes. You can simulate this in a Properties file by selecting a character to be a name separator (typically a period), then using that separator character to define names for your parameters that indicate their grouping. This can easily be extended to multiple levels to allow a hierarchy of grouped parameters.

Once you have groups of config parameters, you might want to implement some kind of inheritance mechanism, whereby you can declare a set of names and values in group A, then declare that group B has the same item values as group A, possibly with some specified exceptions. Or perhaps you would like to be able to set the value of a parameter to be the same as the value of some other parameter, or some combination or transformation of other parameters.

You can continue to add more capabilities to your config file, but once you start getting too complex, you probably want to adopt an existing language syntax to avoid creating something that is complicated to implement and maintain, tedious to document, and difficult to learn and use.

If you do use a language for your config file, you may need to modify your approach in order to be able to implement all of the steps given below. In particular, you should not directly modify your operational objects from the config file, as this violates the separation of config data from the application and makes it more difficult to validate the entire config before activating it. One solution is to make your config file code only set data into the new Config objects that are being created for the reload process. Other solutions are possible, such as setting up a mock execution environment in which code can be validated before being applied, but a detailed discussion of such techniques is outside the scope of this post.

When choosing a format, you might consider whether you plan on maintaining config files through a program (either the application being configured or a separate config maintenance application), or if editing config files with a text editor is sufficient. Some applications maintain their config files in XML format for this reason, as there are many packages that can easily read and write XML files, as well as do basic syntax checking outside of the application being configured. Properties files can also be easily written, but there are many other formats that could be used. This can get tricky if you are trying to use an application to maintain config files when you are using a general purpose language for those files.

No matter what format you settle on for your config files, the same concerns discussed below apply regarding reloading the config.

Config Objects

In the approach described here we store in-memory configuration information in special Config objects that are separate from the operational objects that they configure. Defining separate Config objects gives us these benefits:
  • It allows us to represent multiple configurations simultaneously. In particular, it allows us to load and operate on a configuration that is separate from the currently active configuration.
  • It provides a convenient location to collect the methods that manipulate or otherwise access the configuration parameters.
There should be a set of Config objects that correspond to the different operational objects that can be configured. Each different class of operational object to be configured should have a different custom class of Config object associated with it. An operational class with multiple instances should have a separate instance of its Config class associated with each operational instance.

The various Config objects should be related to each other in the same way as the operational objects are related to each other; for example, if operational object A can have multiple children of type B, then ConfigA should be able to have multiple children of type ConfigB. There should be a single Config object which serves as the root Config object from which all other Config objects can be reached.

If the application is written such that there is a single application-wide active configuration, then the application should have a singleton which is the active root Config object. In the discussion below, I assume that such a singleton exists; if your application has multiple contexts, each with a different set of config info, you should interpret the word "singleton" to refer to the single active root config for the context whose config is being updated.

All of the Config classes can inherit from a standard base Config class that provides implementations of common useful methods such as type-safe calls to get integer and date parameters.

Seven Steps

There are seven steps involved in loading or reloading configuration data: Trigger, Locate, Load, Validate, Activate, Report, and Use. Each of these steps can be considered independently of the others. Each step has its own design decisions and implementation choices. In the approach we are using, the Config objects mentioned above are the common data shared by all but the first two steps.

Trigger

If your application is going to reload its configuration information, it needs to know when to do that. There are a number of options:
  • Your app can check for changes on a regular interval and reload if the source has changed. This is a typical approach used with logging configuration files such as for log4j, in which you can specify automatic reloading with a call to the static configureAndWatch method of DOMConfigurator or PropertyConfigurator.
  • If your app has a command line interface (CLI), you can add a command that reloads the config info.
  • If your app has a web interface, you can add a web page that controls config reloads. This can be a full web page with a form and feedback, or a simple URL that triggers a reload.
  • On a Unix system, a standalone app such as a daemon can be written such that a reload is triggered on receipt of a signal. You can then use the kill command to send the process that signal. SIGHUP (signal 1) is often used by Unix daemon programs for this purpose, some examples being acpid, dnsmasq, postgresd, smartd, smbd, winbindd, and ypbind.
  • For a Java app, you can enable JMX and use that to send commands to your application with a JMX console app such as jconsole or MC4J. JBoss uses this technique, allowing you to reload its log4j config using the JBoss jmx-console.
  • For many apps, you can pretty easily add a web interface, such as by using Jetty for Java apps, for the purpose of allowing control and status feedback.
You may want to limit how often a reload can be triggered to prevent a DOS attack (or the same effect caused by a bug in whatever is producing the trigger).

Locate

Once the app has been triggered to reload the config info, it needs to locate that info. Some options:
  • Assume the data is in the same location as before and reopen that location, such as is often done for a log4j config file.
  • Provide the location of the data along with the trigger. This is easy to do if you have a CLI, web form, or web URL, not so easy if you are using a timer or a Unix signal.
Some applications (such as one that uses the standard Props class in Lift, including the way Lift handles its log4j configuration) have more sophisticated file lookup mechanisms that allow configuration information to be split among multiple files or segregated according to the runtime environment to be used. If you are using a package that looks for one or more out of a set of possible files, and you want to be able to add or remove a config file and then reload, you should check to make sure the package is able to reload files and that it will rescan its set of possible files and not just assume that the same config files should be used as when they were first loaded.

Load

Once the data has been located, it needs to be loaded into memory where it can be manipulated. Note that you should load the data into a new Config object or set of objects so that you can do the validation checks on it before activating it.

You should not have to write the code that actually loads the data, as there are a number of usable options available. As an example, you can store your config data in the standard Java Properties format, then load that data using Properties.load. After reading the data into a Properties object, you can create your custom Config objects from the data in the Properties object.

Validate

Once the config data is loaded into your Config objects, you are ready to validate the new configuration. You should make the following checks:
  1. Ensure that the syntax of all configuration values is correct. Depending on how you loaded the data and converted it to your Config objects, some of these checks may already have been done. If there are any values which have not yet been checked for correct syntax, those values should be checked now.
  2. Perform semantic checks on individual parameters. This includes things such as checking that numbers are within allowable ranges, or that each selection parameter has a value that is one of the allowable selections for that parameter.
  3. Perform validity checks on multiple parameters. This includes situations in which you have two or more parameters that are related and which thus must have values consistent with each other.
  4. Compare the new set of Config objects against the current set to ensure that all proposed changes are allowed. You may decide that some changes are too much work to bother to implement; you can disallow those changes in this step.
With a Config class that corresponds to each configurable operational class, we can put the validation code directly in those classes rather than in the operational classes.

After completing the above validation steps, and assuming there were no errors, you have done all error checking and know that you will be able to switch to the new config without errors, but you have not yet done so.

Errors in any of these steps should be collected so that they are available for Reporting.

Activate

Assuming that the loaded Config objects pass all of your validation tests, it is time to activate the new Config. While conceptually simple, this is the trickiest step.

The key issue here is ensuring that the application works properly in the presence of concurrent access to the config data. You want to make sure that the application cleanly switches from using the old configuration to using the new one, without the possibility that some operations will be performed with part of the old configuration and part of the new one.

There are two basic updates you need to make, which correspond to the two basic approaches to using the data:
  1. Update the active root Config singleton.
  2. Update all operational objects that contain configuration state.
Handling the first approach is pretty easy: inside a synchronized block, update the active root Config singleton. When another thread begins an operational sequence that relies on any config parameters, it reads the current root Config singleton (in a synchronized block) and keeps it in a local variable for the duration of the operational sequence. All queries for config parameters during that sequence are done against the local Config variable, ensuring that the entire sequence uses a single Config even if the Config singleton is updated in the middle of that operational sequence.

If you are using the second approach, updates are a bit trickier. It would be simple if the activation thread could just update the state in the operational objects, but another thread may currently be running and using those operational objects in an active operation. You can't just update the state in all of the operational objects from the activation thread because the operational thread might then pick up the new state in the middle of one operational sequence, and we assume that starting an operational sequence with one state and finishing it with another state will cause problems.

The key to handling changes when using this second approach is to build on how we solved changes to the first approach by capturing the value of the active root Config singleton at the start of the operational sequence. That starting point is the point at which we know (by definition) that it is safe to change over to a new config. When we start our operational sequence, we capture the currently active Config into a local variable, as described above as the solution for changes to the first approach. We then check to see if the config has changed since the last time we started the sequence. We do this by comparing our newly captured Config against the Config that we used the previous time we executed our sequence, which means we need a second variable that stores that previous Config. If the newly captured Config is not the same as the previous Config we used, then we reconfigure our operational objects according to the newly captured Config, then save that as well as the most recently used Config for the next execution.

When using the above solution, if the only time you update the operational state is when a thread starts an operational sequence, and that thread waits for a long time before beginning execution of the sequence, then the switch of the operational state to the new config may not happen for a long time. Despite having validated our new config, it is possible that, due to a bug, the new config will fail when we attempt to apply it to our operational objects, and it is generally better to have that happen immediately when the config is activated rather than much later, when it might not be obvious that the problem is due to the new config. In order to avoid this situation, you should add code to make your threads wake up and apply the new config immediately after it is activated, even if there is no other work for them to do.

If you have multiple independent operational sequences you should separately capture a copy of the active Config at the start of each sequence. However, you need to make sure that each sequence is in fact independent of the others as far as the config parameters that each uses, since when using the above approach you may end up with two threads executing different sequences at the same time with one using the old config and the other using the new config.

If the different threads are related, such that it is not acceptable for one thread to be using the new config while another is still using the old config, then you will have to use a different approach. In this case, you will probably need to write some code to ensure that no thread starts using a new config until all threads have stopped using the old config.

You can do this with two flags, properly synchronized:
  1. config-in-use
  2. ok-to-use-config
The activation thread turns off ok-to-use-config, then waits until config-in-use is zero. At that point it updates the root Config singleton and turns on ok-to-use-config.

The operational threads check ok-to-use-config before capturing the current config. If turned off, they wait until it is turned on. They then increment config-in-use, use the config, and decrement config-in-use when done. Synchronized and try/catch blocks should be used to avoid race conditions and ensure the config-in-use count doesn't get stuck on.

Report

Feedback is important. Ideally, the user should get the following feedback:
  • When loading of an updated config is triggered, the user should get feedback on whether or not the new configuration was activated.
  • If the new configuration was not activated, the user should get feedback on why the new configuration was rejected (i.e. he should see a list of config errors).
  • Ideally, at a later point in time it should be possible for the user to determine what configuration is currently being used and how long it has been active. This is useful in situations where an on-disk config was changed at some point in the past but not loaded into the application.
Generally the reporting feedback channel is related to the trigger mechanism:
  • If you use a CLI command to trigger the reload, that command can print out the feedback.
  • If you use a web page to trigger the reload, the web response page can display the feedback.
  • If you use JMX, the feedback can be returned through that protocol.
  • If you use a web URL, the HTTP response can include the feedback.
If you application does logging, the feedback can be logged to the log file. This can be done in addition to any of the above feedback mechanisms.

Use

It is important to ensure that the config parameters used are consistent throughout an operational sequence, even when a config reload occurs while that sequence is executing, as discussed above in the Activate section above. Once you have handled that, you can move on to other usage aspects.

There are two basic approaches to using the active config parameters:
  1. Use the current Config object directly each time a config value is needed. This is suitable for simple options that are tested each time a specific behavior or feature is desired.
  2. Load data from the current Config object into operational objects. This is necessary when some of the config info refers to state that is managed by an operational object, such as the endpoint for a TCP connection.
The first approach provides for simpler updating of the config data, but sometimes the second approach is necessary for performance reasons or due to how state information is stored in other objects. The timing for when to update config state in operational objects is discussed in the Activate section above.

A minimal implementation of the Config object would provide just a single method to retrieve any parameter by name, such as is provided by the Properties.getProperty method. While this is easy to implement, it does not provide as much protection against programming errors as other approaches described below.

For type safety, you should implement (or use a package that provides) a set of methods with specific return types that match the types of your config parameters. You can then pass in the name of each parameter and not have to type-cast the result.

For maximum type safety your Config object should provide methods specific to each config parameter being retrieved. This ensures not only that you have the correct return type for the parameter, but that you have not accidentally mistyped a parameter name in a call to retrieve its value. (Of course, your unit tests should also catch this error, but you will catch it sooner and more surely with compile-time checks.)

Unit Testing

Keeping the configuration management code in separate Config objects improves the testability of your code. You can write unit tests for your Config objects to test that they properly locate, load (or reload), and validate config files, and you can create a set of mock Config objects that you can use to test how your application responds to different configurations.

A more complete test suite will include tests that verify proper functionality when a reload operation is performed by one thread while one or more other threads are in the middle of processing and using config data. However, a detailed discussion of this kind of multi-thread testing is beyond the scope of this post.

Implementation Options

You can write all of your own config code from scratch, or you can leverage an existing package. Whatever approach you take, you will want to ensure that your application handles all seven of the steps discussed above.

A few packages are listed below, with a discussion of the steps for which they provide support. For bullet items marked no support you will have to write your own code. None of the packages provides support for all of the steps. Even if a package did provide that support, you must still provide application-specific code for validation, activation, and use.

Caveat: Except for Properties, I have not used the packages listed below. My evaluation of their capabilities is based entirely on reading the documentation and examining the source code, so it is possible that I have made some mistakes in that evaluation.

Properties (Java)

The standard Java library includes the Properties class, which can be used for simple applications that require only a few parameters.
  • Trigger: no support.
  • Locate: no support.
  • Load: You can load a properties file with a single call to Properties.load, where you pass in the name of the file to load.
  • Validate: no support.
  • Activate: no support.
  • Report: no support.
  • Use: The Properties.get method will return the value of a property as a String. You can use this directly as a generic call to retrieve config parameters by name, or you can layer your type-safe methods on top of this.

JavaConfig (Java)

JavaConfig (not to be confused with Spring JavaConfig, which is used for Dependency Injection configuration) reads config files using the standard Properties file format. The package provides a generic Config class, which you subclass to create your application-specific config class. It handles a defined set of data types.

JavaConfig specifically does not include any logging, so that it can be used to read the configuration for another logging package.
  • Trigger: no support.
  • Locate: no support.
  • Load: You pass the name of a properties file to the Config constructor, which loads the properties file.
  • Validate: After instantiating your config object, you call the validateConfiguration method on it, which returns a ConfigValidationResult object that contains the validation results. This validateConfiguration method calls all of your getter methods. For each of your methods that throws an exception, the message is collected and made available through the ConfigValidationResult object.
  • Activate: no support.
  • Report: The ConfigValidationResult class collects the error messages from all of your getter methods that throw exceptions, and makes them available
  • Use: The base Config class provides type-safe methods such as getInt and getBoolean that accept a parameter name. In your config class that extends that class, you define a getter method for each of your config parameters. Each of your methods should call one of the underlying type-safe methods, passing it a config parameter name, and return that result. Your method should also perform any validation checks and throw an exception if there are any validation errors.

Apache Commons Config (Java)

Apache Commons Config provides a mechanism to allow config info to be loaded from a variety of sources, such as files or databases. You can mix config info from multiple different sources, such as reading some info from a database and some from system properties, and access it all through a single config object. It supports file includes and value substitution.
  • Trigger: The package org.apache.commons.configuration.reloading provides a mechanism for defining a reload strategy when using file-based configuration, such as reloading on access to a config element if the file has changed, and some support for using JMX to trigger a reload.
  • Locate: You can pass in a relative filename, and the package will look in various locations for a config file of that name to load.
  • Load: You can create a configuration object for a specific data source, such as a file, or you can create a composite configuration object from multiple other configuration objects.
  • Validate: no support.
  • Activate: no support.
  • Report: no support.
  • Use: There are a set of type-safe methods to which you pass an item name and receive back its value.

Configgy (Scala)

Configgy includes logging as well as configuration, so it can use its own config files to configure logging. Its config files look like a cross between an XML file and a Properties file, with hierarchy represented by XML syntax, and individual parameters looking more like Properties. It handles a defined set of data types and has the ability to represent lists of values for a single parameter.

Configgy supports a lot of options when defining parameters, including hierarchy, inheritance, includes, variable substitution (including system properties), and conditional assignment.

Note that Configgy allows the application to set values in the config after it has been loaded into memory. As with any situation in which one datastructure might be shared among multiple threads, you should be very cautious with this capability. In particular, if you have a thread which has read the config and used that data to set state in its own application objects, setting the value in the config object alone may not have the desired effect. Your application can set up a subscriber for changes, which will be called when there are runtime changes to a config value, but you need to handle synchronization of these runtime changes in the same manner as when reloading the entire config. And your application code that is calling the set method must be prepared to handle a thrown exception if a subscriber decides the change is invalid.

The examples given in the Configgy documentation use a Scala object (as opposed to class) and does not discuss reloading, but there is a reload method available on the main object, which should work if you use the approach described above (using a pair of flags) for the case when all threads are related. Also, there are separate config objects being used under the covers, so it should be possible to use those directly, rather than the main object, if you want to be able to switch some threads over to your new config while some other threads continue to use the old config.

If you are writing a Scala application, Configgy is probably your best option.
  • Trigger: There is some JMX support built in; reload is not one of the methods available from the JMX interface, but it should not be too difficult to add it.
  • Locate: Configgy has calls to allow you to set the location of the config file to load. You can call this before calling reload() to control the source for the reload.
  • Load: You call Configgy with a filename and it loads that file and any file referenced with an include statement.
  • Validate: Configgy uses a subscription/callback model to let the application know when data has been changed. Your callback is called with an argument that tells you whether Configgy is doing a validation pass or an activation ("commit") pass. On the validation pass, your callback can throw an exception to indicate that the new value fails validation.
  • Activate: The validate/commit subscription model provides hooks to allow you to write your own validation and activation, but you still need to consider synchronization when using multiple threads.
  • Report: no support.
  • Use: There is a set of type-safe methods to which you pass a parameter name, which can be hierarchical.

Thursday, December 3, 2009

Improve Your Releases

There is more to a good software release than a good program.

If you are releasing software and want it to be successful, you have to do more than just write a good program. You need to consider all of the things the user will want to do with your software before and after actually using it.

You can look at the steps below as an interpretation of the phases of the software lifecycle from the perspective of the user. Depending on how well you do your job, the user will have a better or worse experience with each of these phases. If, for one of these phases, you do nothing, the user is likely to have an unpleasant experience when he gets to that phase.

Develop

This is the phase that most open-source developers focus on. If we were looking at the software life cycle in a little more detail, we would split this phase into three separate phases: design, code, and test. In commercial development these three phases are often handled by separate groups, but from the user's perspective they can be lumped together as being the factors that contribute to the overall quality and usability of the software.

This is the time in which to consider all of the points below so that you can create your system in a way that makes it easy to do the right thing for all of the other phases of the software life cycle.

Release

Once the software comes out of test, it must be packaged up as a Release. It is useful for a user easily to be able to tell what released artifact he has acquired and what version of that artifact he has. The simplest way to handle this is to define a released artifact as being a single file. If you think you need to release a collection of files, they should be packaged up as a single file, such as in zip, tgz (tar-gzip), dmg or iso format. You can then give each file a name and version number to allow the user to identify it.

You may have a product that is composed of a number of other released artifacts. You can bundle these into one larger artifact that is a collection of the other artifacts plus an installer that can invoke the installers of the other artifacts, or that knows how to install those other artifacts directly. Operating system installers work in this way.

Once your released artifact is in a single file and appropriately labeled, it is easy to take the next step: generate and publish a checksum for that file. If, for any reason, the user is unsure about what artifact and version he has, he can then run a checksum on his file and compare it against your official list of checksums. Using a cryptographic checksum provides protection not only against accidental corruption, but against intentional modification (hacking) of the artifact as well. Depending on the level of security desired, you can use md5, sha1, or sha256 for your checksum. Operating system distributions such as Fedora do this, including the additional security step that the published list of checksum values is digitally signed.

Distribute

Your user needs to get your released artifacts. Long ago this used to be done by distributing physical media such as DVDs, CDs, floppies, or tapes. Today most distribution is done over the internet, which makes this step far simpler than it used to be.

Open source projects have easy solutions available through such services as sourceforge and github. Many commercial providers also distribute their software from download pages on their web sites, often with additional security such as restricting web access to customers with accounts, and using license files to enable specific functionality in the installed software.

Given how widespread and well-understood this model is, it makes sense to use it for internal software as well: set up a web site where your users can find all of your released artifacts. If the list of artifacts is small, you can just set up a few directories with files in them and serve up those files with your web server. When the number of artifacts gets large enough so that browsing listings gets cumbersome, you can add a search form. If you need to restrict which of your internal users have access to your downloads, you can do that in the same way as commercial vendors do, with password access or license-file control of the installed application.

Install

The user should be able to install the complete application from the downloaded artifact with a single command, or at most two commands (an unpack command followed by execution of a setup script). Installing a Windows application is generally done by downloading an exe file and then executing it; a Mac install is generally done by downloading a dmg file, double clicking on it, and dragging the app into another folder; a Java install is often done by downloading a jar file and then executing it (such as by running java -jar on it). These are all examples of simple installation mechanisms. Once running, an installer can direct the user to select values for options and installation paths.

In particular, you should not require the user to unpack the software and then manually execute a number of other steps such as moving files around or editing config files. These are steps that should be handled by an installer.

When the files are installed on the user's system, there should be an easy way to determine what version is installed and in use. This information should be easily available in the application, such as in an About menu in a GUI application. If a user might install multiple artifacts from your collection, there should be a simple way to get a list of all artifacts installed and in use along with their version numbers so that you can unambiguously tell what versions of your software are being used at that site.

Support

Finally, the user is using your software. If your software is well designed, well written and well tested, the user should have no problems using it and all will be well. In reality, it is unlikely that no user will ever have any problems with your software. When a user does have problems, what will he do (other than grumble or swear at your software, that is)? Assuming the user is motivated to solve the problem rather than just giving up, he will seek out resources that can provide him with the information he needs to solve his problem. You can make his life easier in this step by providing some or all of the following:
  • A Users Guide or set of guides (tutorial, reference).
  • In-application help (context-specific, page-specific, links to the manual, search, how-to).
  • On-line forums where users can share their problems and solutions.
  • Direct support, via telephone, email, or chat.
If a user experiences a crash or runs into a bug, it might be nice if he can easily submit a crash report or a bug report so that you can more effectively fix the problem. If so, you will want that report to automatically include the list of installed artifacts and versions, as discussed above in the Install section.

Upgrade

If your software is successful you will probably release new versions of it. A user who is already using your software should be able to start using the new version of your software with minimal hassle. As with the initial install, installing an upgrade should be done with at most one or two commands, as it could be with an upgrader that guides the user through whatever questions need to be answered for the upgrade.

You can add an option to your application to check for upgrades and ask the user if he wants to download and install them, saving the user the hassle of separately doing those steps. If you choose to implement this, you should allow the user to disable it. There should also still be a way that the user can download an upgrade (as a single file, just as with an initial install), copy it to another machine, and install it there, in case he is running on a machine that is not connected to the network or is behind a firewall that prevents your automated download from working.

There are two ways in which an upgrade is different from an install, leading to two additional goals for the upgrader:
  1. If the user has any configuration or customization, that should be carried over to the new version.
  2. If the user starts running the new version and soon discovers that it is unusable for him, he should quickly be able to roll back to the previous version.
An approach to handle the first goal is to keep the configuration and customization in a separate directory, such as in the user's home directory, or (for Unix systems) in /etc or (for Windows systems) in the Registry. There can still be problems when upgrading if the format of the config and customization files changes, or if the items being configured and customized have changed between versions. Your upgrader should take care of this.

One relatively easy way to satisfy the second goal is to install each version of the application in a separate directory that contains the version number in the name, then providing a current directory that is a link to the version to be used. Rolling back to a previous version might then be as simple as deleting the current link and recreating it to point to the previous version. Ideally, however, this rollback is also done by a program you provide, in case a rollback also requires any other changes such as to the configuration and customization files.

The upgrade and rollback should of course update the list of installed artifacts and current versions.

Patch

Occasionally you might want to deliver a minor update or bug fix to your software. You might send out one modified file and ask the user to install it in a specific location to fix a bug.

While this sounds like an easy mechanism for quick fixes, in the long run you will be better off ensuring that your upgrade process is streamlined enough that you can package up that one file in an upgrade and use your upgrade process.

The problem with sending out patch files and doing ad-hoc installs like this is that it makes it very difficult to keep track of what is installed at a customer site. If you send out four or five patches and then the customer starts reporting unique bugs, will you know what software is running at that site so you can track down those bugs? You could work on setting up a system to keep track of those patches, but you might as well invest that effort into making your upgrade process easier to use.

Perhaps you think that each customer will have a different set of patches, and you don't want to send the same patches to all of your customers, so you don't want to make them all standard upgrades. If it is really the case that you want to deliver different things to different customers, then you are not really delivering one artifact, you are delivering separate artifacts to each customer. In this case, you should just call them different artifacts, give them their own version numbers, and send out upgrades for those separate artifacts. In that way you can continue to use your standard upgrade process, and you can always know exactly what your customer has by collecting the list of artifacts and their version numbers for all of the artifacts installed at a customer site.

If you really think you need to send out patches, consider the following goals:
  • It should be easy for the user to install the patch with a single command.
  • It should be difficult for the user to make a mistake when installing the patch, such as could happen if he has to manually install files into specific directories or manually edit any files.
  • It should be easy for the user to rollback the patch if it doesn't work.
  • It should be possible for both you and the user to know exactly what version of software is installed at the site, including what patches have been applied, even if there is a patch of a patch.
If it seems to you that implementing a patch mechanism that does all of this is easier than adding some improvements to your upgrade process and perhaps dividing up a couple of your artifacts to more accurately reflect how you are actually installing them, then go for it.

Migrate

At some point one of your users might decide that he wants to stop using your software and move to some other package. If you are a commercial software provider you might think this is not something that should be in your list of goals - why should you help out a competitor? - but if you are interested in doing what is best for your user, you should at least recognize this phase of the software lifecycle and make a conscious decision about it. The better you treat a leaving customer, the more likely it is that he will some day be a returning customer.

To support your users in this step, you should provide export tools that allow the user to export all of his data from your application in a standard format. Depending on the application, this might mean exporting a CSV file, an XML file, an Open Document file, or something else.

If you also implement an import capability that reads the same standard file format as your export produces, this could help you in the future if you ever change your internal storage representation from one version to the next: just export from the old version into a file using the standard format, upgrade to the new version, and import that file.

Uninstall

Whether or not a user chooses to move to a different product, he may eventually decide he is done using your software and he would like to remove it from his system. As with the install, it should be possible for the user to uninstall your software with a single command. If an application was installed simply by unpacking it, that single command might be to remove that unpacked directory. With a more complicated installation, uninstallation is likely also to be more complicated, making an uninstaller program more important.

If you have set up your application such that the user-customized portions are separate from the standard install, your uninstaller can give the user the option of keeping those portions. Similarly, if the application maintains user data in its own directories, you should get confirmation from the user before deleting those files and give the user the option of keeping them.

You might also want to consider how you want your installer to behave if the user runs the uninstaller, keeps his customizations and data, then runs the installer. A user might want to do this to downgrade to a previous version if you do not otherwise provide a simple solution for that. Or perhaps you treated a departing customer well enough that he is now returning to your product, in which case he might be pleased to find that his old preferences and customizations are still available.

Wednesday, November 4, 2009

Overriding vals as Optional Parameters

For simple cases you can use Scala vals, selectively overridden, as a way of implementing optional parameters. Overriding can also be used for other interesting tricks.

Contents

Optional Class Parameters

In Java, a typical idiom for initializing an object that has a large number of optional parameters, of which only a few usually get set, is to construct the object and then call setter functions to customize each of the optional parameters. While this technique can be convenient, it leaves open the possibility that the setter might get called later on in the objects lifecycle at a time when changing that value could cause problems.

One solution to this problem is to use the builder pattern. This solution is available in Scala as well, and can be taken a step farther than in Java by using the type-safe builder pattern.

The type-safe builder can be overly complicated for many situations. Sometimes it would be nice to have something simpler than even the simplest of builders.

Scala 2.8 will have named parameters with default values, which will make it pretty easy to create classes that have optional parameters, although you might not want to do this if you have 30 optional parameters. Meanwhile, there is another approach you can use: overriding vals.

The approach is pretty simple: you define a base class with a constructor that includes all of the required parameters, and you then add a val for each of the optional parameters. When you want to create an instance of that class that sets some of the optional parameters, you create an anonymous subclass by adding a set of braces after the new statement that creates the instance, and inside the braces you override each val that you want to set.

In this example we define a Car class that represents a few pieces of information about a car. model and color are required parameters and appear in our constructor. Our optional parameters are hasRadio and hasSunRoof, so we make those vals rather than constructor parameters, and we assign them their default values. We include a toString method so we can easily see the results.

class Car(model:String, color:String) {
    val hasRadio = false
    val hasSunRoof = false

    override def toString() = {
        "Car{"+
            "model="+model+ 
            ",color="+color+
            (if (hasRadio) ",hasRadio" else "")+
            (if (hasSunRoof) ",hasSunRoof" else "")+
        "}"
    }
}
The normal use would be to call the constructor with no additional arguments:
val c1 = new Car("Ford", "red")
println(c1)

//Car{model=Ford,color=red}
To specify one of our optional arguments, we add a code block to the new call, which creates an anonymous subclass in which our val overrides the default:
val c2 = new Car("Chevy", "blue") { 
    override val hasRadio = true 
}   
println(c2)

//Car{model=Chevy,color=blue,hasRadio}
We can pass in values from the caller's context rather than constants:
val myHasSunRoof = true
val c3 = new Car("Honda", "white") {
    override val hasSunRoof = myHasSunRoof
}
println(c3)

//Car{model=Honda,color=white,hasSunRoof}

Optional Trait Parameters

You can use this same approach to pass in values for instance variables in traits, which don't have constructor parameters. For example, say we define a trait for an optional Touring package for our car:
trait Touring {
    val hasNavSystem = false
    val hasExtraSuspension = false
    val hasTowHitch = false
    val hasRunningBoards = false

    override def toString() = {
        super.toString()+
            "+Touring{"+
            (if (hasNavSystem) "navSystem," else "") +
            (if (hasExtraSuspension) "extraSuspension," else "") +
            (if (hasTowHitch) "towHitch," else "") +
            (if (hasRunningBoards) "runningBoards," else "") +
        "}"
    }
}
Now we can create an instance of a Car with Touring and pass in values for some of those "optional constructor parameters" defined in the Touring trait:
val c4 = new Car("Honda","white") with Touring {
    override val hasSunRoof = true      //from Car
    override val hasNavSystem = true    //from Touring
    override val hasRunningBoards = true  //from Touring
}

println(c4)

//Car{model=Honda,color=white,hasSunRoof}+Touring(hasNavSystem,hasRunningBoards,}
NOTE: Due to a bug in older versions of Scala, at least through 2.7.6, overriding a val on a trait as in the above example does not work. This does work properly in Scala 2.8.0 (at least it does in the 20091006 nightly build).

Early Definition

You may have a situation in which some of the vals that you are initializing in a trait or class depend on other vals. In this case, overriding a val as we did above may not give you the result you want: the initializer of the superclass runs to completion before the initializer of the subclass, which means all of the vals in the superclass get set before any of the overriding vals are evaluated.

For example, say we modify our Touring trait by adding a maxTowWeight value, as shown in bold below:
trait Touring {
    val hasNavSystem = false
    val hasExtraSuspension = false
    val hasTowHitch = false
    val hasRunningBoards = false
    val maxTowWeight = if (!hasTowHitch) 0 else
        { if (hasExtraSuspension) 1500 else 1000 }

    override def toString() = {
        super.toString()+
            "+Touring{"+
            (if (hasNavSystem) "navSystem," else "") +
            (if (hasExtraSuspension) "extraSuspension," else "") +
            (if (hasTowHitch) "towHitch," else "") +
            (if (hasRunningBoards) "runningBoards," else "") +
            "maxTowWeight="+maxTowWeight +
        "}"
    }
}
When we instantiate a Car with Touring the constructor code for Touring executes before the constructor code for the new class. In particular, val maxTowWeight gets evaluated before the overriding values are evaluated, so it always ends up with a value of zero:
val c5 = new Car("Honda","white") with Touring { override val hasTowHitch = true }

println(c5)

//Car with Touring = Car{model=Honda,color=white,hasRadio=false,hasSunRoof=false}+Touring{towHitch,maxTowWeight=0}
Scala provides a mechanism to address this issue: Early Definition (Scala Language Specification, section 5.1.6). The vals that you specify in the Early Definition block are evaluated in the context of the calling class, then that set of values is placed into the context of the new class being instantiated such that all of those values are available at the beginning of the process of instantiation, even before the initializer for Object is executed. In this way, any expression which uses one of those vals will have access to the value provided in the Early Definition.

It could be used with our Car example like this:
val c6 = new { override val hasTowHitch = true } with Car("Honda","white") with Touring

println(c6)

//Car with Touring = Car{model=Honda,color=white,hasRadio=false,hasSunRoof=false}+Touring{towHitch,maxTowWeight=1000}
A class definition for the above example could look like this:
class TouringCarWithHitch(name:String, color:String) extends {
            override val hasTowHitch = true
        } with Car(name,color) with Touring {
    //normal class overrides and additional elements here
}

val c7 = new TouringCarWithHitch("Honda","white")
//c7 is the same as c6 (but we have not implemented ==)

Required Trait Parameters

If you want to define a trait that has required parameters rather than optional parameters, you can omit the value from the declarations and instead specify only the type, which causes the val to be abstract. For example, if we want to make the hasTowHitch and hasNavSystem parameters to our modified Touring trait be required, that would look like this:
trait Touring {
    val hasNavSystem:Boolean   //abstract (no value)
    val hasExtraSuspension = false
    val hasTowHitch:Boolean    //abstract (no value)
    val hasRunningBoards = false
    val maxTowWeight = if (!hasTowHitch) 0 else
        { if (hasExtraSuspension) 1500 else 1000 }

    override def toString() = {
        super.toString()+
            "+Touring{"+
            (if (hasNavSystem) "navSystem," else "") +
            (if (hasExtraSuspension) "extraSuspension," else "") +
            (if (hasTowHitch) "towHitch," else "") +
            (if (hasRunningBoards) "runningBoards," else "") +
            "maxTowWeight="+maxTowWeight +
        "}"
    }
}
Now when we declare a concrete instance of this class, we are required to define values for those two variables else we will get a compiler error. Since the base declaration is now abstract, we omit the override keyword on those vals:
val c8 = new Car("Honda","white") with Touring {
    override val hasSunRoof = true      //from Car
    val hasNavSystem = true             //from Touring; required
    override val hasRunningBoards = true  //from Touring; optional
    val hasTowHitch = false             //from Touring; required
}

println(c8)

//Car{model=Honda,color=white,hasSunRoof}+Touring(hasNavSystem,hasRunningBoards,maxTowWeight=0}

Abstract Class Parameters

Sometimes it is convenient to use an abstract val rather than a constructor parameter for abstract classes. For example, say you have a Service and you want to define a set of case classes for service messages. The base class should have a reference to the Service object so that it can easily be processed by generic service methods, but each case class should also have the same reference as a case value for easy matching. For consistency, since these are the same value, the name should be the same. You could do this by defining the base class with one parameter declared as a val to make it accessible, then define the case classes to override that value, like this:
abstract class Service
abstract class ServiceMessage(val service:Service)
case class ServiceStart(override service:Service) extends ServiceMessage(service)
case class ServiceStop(override service:Service) extends ServiceMessage(service)
The case class automatically adds a val keyword to each of our parameters, so we need to specify the override keyword, but can omit the val keyword.

We can simplify our case classes a bit by changing the base class val from a constructor parameter to an abstract val, like this:
abstract class Service
abstract class ServiceMessage { val service:Service }
case class ServiceStart(service:Service) extends ServiceMessage
case class ServiceStop(service:Service) extends ServiceMessage
Not only have we dropped the override keyword, but we are also not passing the service parameter to the superclass. The implied val keyword on the case class parameters creates a concrete instance of the service parameter that overrides the abstract value defined in the base class.

Type Parameters

Just as scala has value parameters, concrete value members and abstract value members, it likewise has type parameters, concrete type members and abstract type members. The approach used above on values can generally by applied to types as well: rather than defining a class with a type parameter, you can often define that class with a type member. If the type is a required type that must be overridden by the extending class, make the type member abstract; if you want the subclass to be able to default to the type used in the superclass, use a concrete type and let the subclass use the override keyword if it wants to override that type.

Bill Venners has a nice blog post where he discusses the question of when to use a type parameter and when to use an abstract type member, with a reference to an interview with Martin Odersky where he talks about abstract type members in comparison to instance variables.

Caveats

Although in many ways you are free to choose between using a constructor parameter versus a class member, they are not entirely equivalent. In particular, once you start building up class hierarchies using abstract and concrete members with overrides, you have to be careful that the initialization order is what you expect. In the Early Definition section above I gave one example of how values can fail to initialize correctly due to ordering issues. That one is pretty easy to understand, but they can sometimes be far more subtle and hard to spot.

One thing you can do that will sometimes fix such problems is to use the lazy keyword on your value members in order to get lazy initialization. This causes initialization of the value to be delayed until the first time it is used, rather than being eagerly initialized when the class is initialized. Note that if you declare a concrete variable as lazy, then an overriding instance of that variable must also be declared as lazy; if the original concrete variable is not lazy, the overriding variable can not be lazy.

Note that overriding a val in Scala is not the same as declaring a variable of the same name in a subclass in Java. Consider this Java test program Test.java:
public class Test {
    public static void main(String[] args) {
        (new Test1()).test1();
        (new Test2()).test1();
        (new Test2()).test2();
    }
}

class Test1 {
    public int t = 1;

    public void test1() {
        System.out.println("t="+t);
    }
    public void test2() {
        System.out.println("t="+t);
    }
}

class Test2 extends Test1 {
    public int t = 2;

    public void test2() {
        System.out.println("t="+t);
    }
}
and the apparently equivalent Scala test program Test.scala (where I have used Java-like syntax where possible so that you can run "diff" on the two files):
object Test {
    def main(args: Array[String]) {
        (new Test1()).test1();
        (new Test2()).test1();
        (new Test2()).test2();
    }
}

class Test1 {
    val t = 1

    def test1() {
        System.out.println("t="+t);
    }
    def test2() {
        System.out.println("t="+t);
    }
}

class Test2 extends Test1 {
    override val t = 2

    override def test2() {
        System.out.println("t="+t);
    }
}
Copy these out to Test.java and Test.scala, then compile and run each one (don't try to compile both and then run both in the same directory, as the class files will collide). The Java test prints this out:
t=1
t=1
t=2
The Scala test prints this out:
t=1
t=2
t=2
Note the difference in the middle line, where we have called Test2.test1(). The Java program prints 1, but the Scala program prints 2. This is because the declaration of t in Test2 in Java does not override the value in Test1, it shadows it. The Test1 value of t is still there, and it used by any method in Test1 that refers to that variable.

In Scala, by contrast, references to t in Test1 refer to the overridden value provided by Test2. Scala can do this because, consistent with the Uniform Access Principle, a variable in Scala is accessed by a pair of functions to get and set its value. When a value is overridden, that creates new access functions in the subclass that override the access functions in the base class.