Friday, 28 December 2007

Java: The Closures Controversy

Joshua Bloch, one of the Principal Engineers at Google and author of the book Effective Java, gave a lecture that was published on Parleys.com recently about the forthcoming support for closures in Java.

Near the beginner of the lecture, Joshua explains how Java was designed from the ground up to be a practical language with a job to do rather than an academic exercise in language design and stresses that this remains an important goal for Java's evolution. In particular, he uses the phrase "Don't fix it until it chafes" to emphasize that a practical problem must be severe before changes to the Java language itself will even be considered.

The last major change to Java was generics, or parametric polymorphism as it is referred to in functional languages. Joshua states that Java is "not doing too well" in the face of the complexity that was introduced by generics, citing an example from Angelika Langer's 427-page Java generics FAQ.

In essence, Joshua explains why he thinks the proposed designs for Java's closures are too grand and should be reduced to only two special cases with absolutely minimal changes to the Java language. In particular, he gives the example of a trivial curry combinator written using the proposed style for Java's new closures:

static  { A1 => { A2 => R } } curry({ A1, A2 => R } fn) {
return { A1 a1 => { A2 a2 => fn.invoke(a1, a2) } };
}

For comparison, here is that function written in the statically-typed functional programming language OCaml:

let curry f a1 a2 = f(a1, a2)

Note the complete lack of types in the OCaml source code thanks to the language's powerful type inference.

Type inference is an essential component of statically-typed functional programming languages like OCaml, Haskell and F# and the lack of type inference in Java means that closures quickly become prohibitively complicated in all but the most trivial cases. Joshua's point is, quite correctly, that burdening Java with the complexity of full-blown closures when they are crippled by the absence of other language features is not constructive.

Joshua goes on to explain that closures are a general solution to some practically important problems in Java programming. Specifically, he cites fine-grained concurrency and resource management. The former refers to passing snippets of code to another thread for execution and the latter refers to using a combinator to encapsulate the undoing of state (e.g. closing a file handle)· He gives the following code as an example of this current deficiency in Java:

static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
closeIgnoringException(in);
closeIgnoringException(in);
}
}

private static void closeIgnoringException(Closeable c) {
if (c != null) {
try { c.close(); } catch (IOException ex) { // ignore }
}
}

In the statically typed functional programming language F#, this is just:

let copy infile outfile =
use outstream = new StreamReader(File.OpenWrite outfile)
use instream = new StreamReader(File.OpenRead infile)
while not instream.EndOfStream do
instream.ReadByte() |> outstream.WriteByte

Note how F#'s use binding automates the closing of the stream at the end of its scope using .NET's uniform IDisposable interface.

Overall this lecture does a beautiful job of elucidating some simple practical problems that Java still struggles with. Joshua's concludes that the Java language should not be drastically altered but a new language should be created that addresses these problems. Joshua even cites the Scala programming language as one of the promising new languages for the JVM.

However, the Scala programming language is very much a research prototype and not a language for industry with a job to do. We were actually hoping that Joshua was going to announce a newer and more modern language for the JVM developed by Google that was designed and built for industrial use. Microsoft are already a long way down this road with their F# programming language and it seems a great shame that the Java platform remains so far behind. If Sun fixed some of the problems with the JVM itself, such as the lack of tail calls, and implemented a better language for the JVM we would surely be among the first adopters. Until then, we shall continue to use languages like OCaml and F# for our commercial work and not Java and the JVM.


Monday, 24 December 2007

Immutable stacks in OCaml/F# and Scala

The Scala programming language diverges from the ML family of languages (including OCaml and F#) in many respects and some of these can be seen in the simple immutable stack implementation given in the book "Programming in Scala".

The following implements a simple immutable stack in Scala 2.6:

sealed abstract class Stack[+a] {
def push[b >: a](x: b): Stack[b] = new NonEmptyStack(x, this)
def isEmpty: boolean
def top: a
def pop: Stack[a]
}
object EmptyStack extends Stack[Nothing] {
def isEmpty = true
def top = throw new Error("EmptyStack.top")
def pop = throw new Error("EmptyStack.pop")
}
class NonEmptyStack[+a](elem: a, rest: Stack[a]) extends Stack[a] {
def isEmpty = false
def top = elem
def pop = rest
}

The following is an equivalent implementation in OCaml or F#:

module Stack = struct
type +'a t = Empty | NonEmpty of 'a * 'a t
let is_empty = (=) Empty
let push h t = NonEmpty(h, t)
let top = function
Empty -> raise Not_found
| NonEmpty(h, _) -> h
let pop = function
Empty -> raise Not_found
| NonEmpty(_, t) -> t
end

The idiomatic ML solution is clearly substantially more concise. There are several reasons for this:

  1. OCaml and F# infer all types in this (and most other) cases whereas the argument and return types of the functions and parameters of parent classes are all specified explicitly in the Scala.
  2. Covariance is automated by OCaml and F# but Scala requires covariance to be introduced explicitly in the definition of the abstract class and repeated in all derived classes, and then handled explicitly in the definitions of member functions such as push.
  3. OCaml and F# automate the hoisting of immutable expressions like the empty stack whereas Scala requires the distinction to be added explicitly by declaring NonEmptyStack as a derived class but EmptyStack as an individual object.
  4. The type of the empty stack must be given explicitly in Scala but the type is polymorphic so Scala introduces the identifier Nothing to denote this unknown type. This is inferred automatically by OCaml and F#.
  5. The module system allows all definitions (type and function) to be encapsulated in a single module called Stack in the OCaml and F#. In contrast, the Scala gives three different object/class definitions that redundantly contain Stack as a substring in their names.

These characteristics of Scala make it more verbose than OCaml and F# in this case. Comparisons between Scala and other modern functional programming languages are surprisingly rare: the Scala community typically compare Scala to other JVM languages. Thus we shall endeavour to describe the similarities between Scala and other languages in future posts.