Wednesday, June 17, 2009

Scala Using RAISIN

last time, we touched on RAISIN, and considered Java's inability to support this programming style to be an important deficiency of the language.  We also promised to explore whether Scala could emulate the C# approach to deterministic destructors.  We take up that challenge presently, and we're going to find that a wide variety of Scala features all come together to make this happen.

Implementing RAISIN is a little tougher than our Ruby "unless" modifier, where the task was pretty narrow and well understood.  So before we begin, let's capture the goals we should set for emulating -- and surpassing -- the C# "using" syntax inside Scala.
  • Beautiful, readable code
  • Obliging the user to do very little
  • Handling multiple resources at once
  • Preventing stale objects from being accessed
  • Prefer immutable & avoid nulls
  • Intelligent exception handling
  • Flexible enough for arbitrary resources
Beautiful, readable code

This is always the prime directive.  Suppose we had our FileHandle class, and we have to ge rid of its associated reource after we use it.  We should tolerate nothing uglier than what we'd see in C#.

// Scala wishful thinking
//
val handle = new FileHandle("myfile")
using(handle) {
// Either of the following methods might
// throw, but that's okay.
//
handle.read
handle.write(42)
}

Obliging the user to do very little

We really want to avoid having to repeast all the try-finally scaffolding in the user's code, which Java would require.  We also don't wan tht use to have to understand the details of how to free up the resources.  Maybe something as simple as...
import csharp._
...should be sufficient to make the using syntax available to the programmer's code.

Handling multiple resources at once

Rather than nesting one using clause inside another, it would be nice to follow C#'s practice of allowing multiple resources inside one using statement.  This also aligns with th functionality afforded by C++, in which we can put multiple objects on the stack inside the same block, illustrated below.

// C++
{
FileHandle const h1 = // details omitted
FileHandle const h2 = // details omitted

// Use h1 and h2 freely here. Even if the
// construction of h2 failed, h1 still
// gets released. That's important
//
}

Preventing stale objecgts from being accessed

This is an opportunity for our Scala solution to shine.  Reconsidering our first example above, We'd like the handle to have the smallest possible scope.

val handle = new FileHandle("myfile")
using(handle) {
// Either of the following methods might
// throw, but that's okay.
//
handle.read
handle.write(42)
}

// It would be nice if we could somehow make the
// compiler prevent spurious accesses of the handle
// down here. We want to deny access to disposed
// objects.

Prefer immutable & avoid nulls

We'd like to use val rather than var wherever we can.  This is analogous to using Java final when declaring variables.  We'd also like to be assured that the resource is constructed correctly, and not null.

These desires may may compel us to put the initialization, meaning the resource acquisition, somehow inside the using clause where it can be managed well.

Intelligent exception handling

It's a well known coding practice in C++ to code destructors so that they do not emit exceptions.  However, no such convention exists for common Java classes.  For example, the java.io.File.close method throws java.io.IOException.  We need a way to handle such exceptions intelligently.

Flexible enough for arbitrary resources

In C++, any class can have a meaningful destructor, so previously designed classes can be used in the RAISIN style.  In C#, we're constrained to use only classes that inherit from the IDisposable interface, and the cleanup has to be done in the dispose method.

This means that ordinary classes like java.io.File, which has a close method instead of a dispose method, will pose some difficulties when trying to wrap it in a C#-like "using" clause.  Yet, Scala is powerful, and it's a reasonable goal to overcome these limitations.

Will all these goals in mind, let's not try to bite off too much at once.  Last time, our zeroth cut defined a Disposable trait and a FileHandle that extends it.  This time, we'll also want a using function that accepts a Disposable object and a block of code to be executed.

// First cut...
package csharp

object Using {
def using[T <% Disposable](resource: T)(block: => Unit) {
try {
block
}
finally {
resource.dispose
}
}
}

There's a lot going on in that method, so let's tease it apart carefully.  First, it's a parameterized function, where the resource argument must be of type T.  The <% notation is a view bound.  It means that type T must inherit from Disposable or be transformable into Disposable by an implicit.

(It's not obvious yet why we need view bounds, or even an upper bound.  This is just a little adumbration for how we're going to achieve some of our trickier goals, such as "preventing stale objects from being accessed," and "flexible enough for arbitrary resources."  We won't get there in this post, but have patience.)

Second, the using method has two argument lists, rather than a single list of comma delimited arguments. Put another way, using is a curried function, as evidenced by two sets of parentheses instead of just one.  This syntax allows the second argument to be a block of code in curly braces, rather than something inside using's parentheses.

Third, note that the arrow notation implies that the block is passed by name, not by value.  This means that the code won't actually execute until block is called inside the try clause of the using method.  It does not execute before using is entered.

Since our toy FileHandle class (defined in a previous post) inherits from Disposable, then we can write the following.


import csharp.Using._

object Main {

def simple_usage = {
val handle = new FileHandle("simple")
using(handle) {
handle.read
handle.write(42)
}
}

// details omitted

That's not bad for a first cut.  We've achieved our first two goals, but we still have a long way to go in future posts to make progress on the others.

In summary, we've taken some steps towards implementing RAISIN in Scala, taking the C# using syntax as a model.  Along the way, we've seen view bounds, curried functions, and pass-by-name.  The latter two language features allow the user's code to be beautiful.

No comments: