Monday, February 9, 2009

3 Scala gotchas

It's been over a year since I bought my "Programming In Scala" book by Martin Odersky. Since then, a lot has changed. There are now 3 more Scala books in the works: a "Programming Scala" by Venkat Subramaniam (Pragmatic Bookshelf), "Beginning Scala" by David Pollak (Apress) and another "Programming Scala" by Alex Payne (of Twitter fame) and Dean Wampler (by O'Reilly). Scala is the key actor in a couple of new software projects (pun intended), including one at Apache (ESME, to which yours truly is a committer; ok, I have no shame).

There's no perfect language, though, and you can't say you know a language until you know its warts. So here's my share of traps, which wasted a lot of my time- I certainly hope you discover them before you waste yours. Of course, these don't mean the language is ill-designed; if only minor corner cases like these can be discovered, this would suggest that a lot of thought has been put in the language.

Main method in companion objects



object Test {
def main(args: Array[String]) {
println("Running!")
}
}


You copy & paste a simple example and compile it. Great, everything works and you're happy. So now you read about companion objects and decide to use your main method so you can test the companion class. You start with a dummy and (if you're cautious), try to compile right away:


class Test {
}


Good, it compiles. Running it, though, results in a NoSuchMethodException for main. Huh? Well, there's a main method only in singleton objects, not in companion objects.

Variables and constants in pattern matching




val Extension = "xml"
"json" match {
case Extension => "Wrong!"
}


Here, you want to match on the contents of a variable. It works as expected: in the above example a MatchError is thrown, as expected. So suddenly you decide that you want to rename the variable...


val fileExtension = "xml"
"json" match {
case fileExtension => "Wrong!"
}


Boom! Now pattern matching matches things it shouldn't! Actually, it matches everything you throw at it. What has happened? Well, Scala checks the name in order to decide if it's an existing constant (capitalized first letter) or a new variable name it should bind (lower-case first letter). So in the last example a new variable called "fileExtension" was created, which shadows the existing variable name.

Always returning Unit from methods




def test1 {
println("test1")
}

def test2 = {
"test"
}


No surprises here- one method prints a String, the other returns one. So then you suddenly decide that you want only one method to this (contrived) and merge these two:


def test {
println("test1")
"test2"
}

println(test)


...and the output is not what is expected: only "test1" is printed. What's wrong here? In Scala, if you omit the equals sign after the method declaration and before the method body, the result is always Unit (same as void in Java).

Monday, February 2, 2009

Creating a Clojure GUI Application with SWT

In my explorations of Clojure, I have been developing a desktop GUI application that uses SWT, the graphics toolkit that Eclipse uses. Once you get going, SWT is fairly intuitive to program in with Clojure, but you might need a little help to get started. Here is an example of a barebones app in SWT:
(ns swttest
(:import (org.eclipse.swt.widgets Display Shell)
(org.eclipse.swt.layout GridLayout)
(org.eclipse.swt.events ShellAdapter)))

(defn create-shell [display shell]
(let [layout (GridLayout.)]
(doto shell
(.setText "SWT Test")
(.setLayout layout)
(.addShellListener
(proxy [ShellAdapter] []
(shellClosed [evt]
(System/exit 0)))))))

(defn swt-loop [display shell]
(loop []
(if (.isDisposed shell)
(.dispose display)
(do
(if (not (.readAndDispatch display))
(.sleep display))
(recur)))))

(defn begin []
(let [display (Display.)
shell (Shell. display)]
(create-shell display shell)
(.setSize shell 700 700)
(.open shell)
(swt-loop display shell)))

(begin)
On Mac, you will need to add the "-XstartOnFirstThread" command line option or your application will crash soon after launching.

Update:
I originally left out the line "(create-shell display shell)". The code above is now corrected.

See also: