Wednesday, May 27, 2009

Ruby Unless Scala

Now that I'm programming primarily in Scala, I find myself missing a couple of cool tricks that Ruby offers. For example, it's neat to be able to put "unless" modifiers at the end of a line. Even if you've never seen Ruby (or Perl) before, it's easy to guess what the following code does.

# Ruby
print total unless total.zero?

It should be no surprise that if the total is zero, then nothing happens. But if the total is not zero, then it's printed.

To my knowledge, Scala has no such concept. However, with all the power Scala offers for creating internal DSLs, it might be fun to try to emulate this syntax. This post is more about demonstrating what can be done with Scala than it is about championing the use of unless modifiers in one's code.

My first attempt failed. I thought I would create a RichBoolean, analogous to a RichInt, so that I could effectively add some methods onto the Boolean class. My new class needed an "unless" method, but it had to be right associative. So, borrowing the trick use in the cons operator, it would have to end in a colon.

class RichBoolean(b: Boolean) {
def unless_:(block: => Unit) {
if (!b) block
}
}

implicit def booleanToRichBoolean(b: Boolean) = {
new RichBoolean(b)
}

There's a lot going on up there, so let's try to tease it apart and explain it. The RichBoolean class is basically a wrapper around the Boolean class. We've effectively added an "unless_:" method to that class. The implicit function tells the compiler to convert a Boolean into a RichBoolean whenever it appears that someone is trying to call an "unless_:" method on it.

This is the standard Scala way to add methods to a class. Some languages are more open, and allow the addition of methods directly after the class is defined. Scala offers the same freedom, but with better control. Unless you're importing the booleanToRichBoolean function, you don't get the automatic conversion. I know that some folks are nervous about implicits. But because of this control, I find them safer than open classes.

Another noteworthy feature of the above is the arrow symbol. This implies that the block is being passed into the "unless_:" method by name, and not by value. In other words, we hope that the block doesn't get evaluated before "unless_:" executes, but only inside of that method when b is false.

Passing by name is a remarkably powerful language feature. To the imperative programmer, it might seem like passing by reference in C++ or Fortran, but it's actually more subtle. We're not passing the address of the result of some calculation. We're actually passing a pointer to the code that computes the result. Methods that accept pass-by-name have the option to skip the calculation entirely, when that makes sense. Consider how efficient that can make a logging API!

Finally, Scala forces us to include the underscore in the "unless_:" method name, so that there's no ambiguity about whether the colon symbol is part of the lexeme. There's an important lesson here that's more widely applicable than this example. Never end lexemes with underscores. If they happen to wind up next to a colon, they may run into trouble.

I tried to test out this code with a little function. I could pass either true or false into it and see what happened. It compiled fine. It just didn't do what I expected.

def demonstrate_ruby_syntax(flag: Boolean) = {
println("flag is " + flag) unless_: flag
}

Sure enough, flag gets promoted to a RichBoolean, and the infix "unless_:" fires. But no matter whether I pass in true or false, the println always executes. This is a little surprising because we passed block by name and not by value.

I'm at a bit of a loss to explain this. A little instrumenting showed that the block containing the println statement is executing outside the "unless_:" method, and not inside it.

By way of comparison, suppose we used the cons operator (::) to construct a List[Int] as follows...

val list = 1 / 0 :: Nil

This blows up because of the divide by zero, but the stack trace reveals that the exception occurs before getting into the cons method. However, the cons method scaladocs say that the argument is passed by value, not by name, so we'd expect exactly that here.

So, unable to make my RichBoolean idea work, I next tried to put a wrapper class around the block itself. This had the advantage of letting me get rid of the colon cruft on the unless method name. I also don't think it's any more dangerous, despite the implicit, because the unless method signature admits only a Boolean.

package ruby

object Unless {

class UnlessClass(block: => Unit) {
def unless(b: Boolean) = {
if (!b) block
}
}

implicit def
unitToUnlessClass(block: => Unit): UnlessClass = {
new UnlessClass(block)
}
}

Happily, this approach worked. It also demonstrates a neat fact. The implicit function accepts block by name, and the UnlessClass constructor does too. Yet, block doesn't execute until the unless method is called with a false argument. This means that the Scala compiler is smart enough to let the by-name cascade through (at least) two calls.

All I have to do now is...

import ruby.Unless._

// details omitted...

def demonstrate_ruby_syntax(flag: Boolean) = {
println("flag is " + flag) unless flag
}

... and my Ruby-esque unless modifier syntax works as expected. The printing only occurs when the flag is false.

There's one more enhancement we can consider. Our code only compiled because println returns Unit. But what if we had some other routine that returned some other type? In such a case, we're relying on the side effects, and not the computational result of the function. This is an imperative rather than functional style, but since Scala lives in both worlds, it would still be nice to be able to use the unless modifier syntax. Consider the following contrived example.

def myfunc(flag: Boolean): Int = {
println("myfunc flag is " + flag)
42
}

Happily, generics can come to our rescue. By parameterizing our UnlessClass, we can implicitly convert to it from arbitrary types.

class UnlessClass[T](block: => T) {
def unless(b: Boolean): Unit = {
if (!b) block
}
}

implicit def
toUnlessClass[T](block: => T): UnlessClass[T] = {
new UnlessClass[T](block)
}

Note that our new unless method still returns Unit because we only use this construct where the return value of methods like myfunc are deliberately discarded.

def demonstrate_ruby_syntax(flag: Boolean) = {
myfunc(flag) unless flag
}

In summary, by emulating the Ruby unless modifier, we've demonstrated a few of the Scala language features that allow rich DSLs to be created. Along the way we learned about right associativity, implicits, passing by name, and generics.

No comments: