The path on the road to learning Scala usually involves using the REPL. this is a very handy way of trying out functions you write easily and quickly, without having to set up an entire IDE environment. Scala’s latests stable release Scala 2.7.5. Scala 2.8 will be out in a couple of months, as things stand now. This new release will contain a number of enhancements and new features, and if you can’t wait to try this out, you should check out and use the trunk. I’ve previously blogged about starting with Scala, and also hinted at trying this out. In this blog, I’ll show some more concrete examples of the and a few neat things that you can do if you’re willing to take this step.
Get the trunk
To use the trunk, you can either check out the sources and build it yourself, our take the nightly builds. You’ve probably set a SCALA_HOME environment variable, so just point it to the newly created build and fire up the REPL. If all has gone well, you should see something like the following:
Welcome to Scala version 2.8.0.r18341-b20090718103640 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_14). Type in expressions to have them evaluated. Type :help for more information. scala>
Using the trunk, the first thing you can profit from is using the redesigned and improved Scala collection library.
It has undergone a large redesign effort, largely done by Martin Odersky himself. It has been checked in and been living in the trunk for a while now, so using the trunk you can have the advantage working with it. The new API is explained in the Scala Improvement Proposal, an excellent and must read for understanding the design of Scala’s collections.
Using the revamped REPL
Paul Phillips not only solves one bug after the other in the trunk, he also has the habit of pimping the REPL on a regular basis.
A very nice feature is that there is code completion, very handy if you don’t have the complete Scala API in your head. First, define a variable and after typing the variable (and possibly the start of a function) hit tab:
scala> val l = List(1,2,3) l: List[Int] = List(1, 2, 3) scala> l AbstractStringBuilder Annotation Appendable Application Array AssertionStatusDirectives BigDecimal BigInt Boolean Byte Cell CharSequence Character CharacterData00 CharacterData01 CharacterData02 CharacterData0E CharacterDataLatin1 CharacterDataPrivateUse CharacterDataUndefined Class ClassLoader ClassfileAnnotation Cloneable Comparable Compiler ConditionalSpecialCasing Console CountedIterator Deprecated Double Either Enum Enumeration Equiv Float Fractional Function GenericRange Immutable InheritableThreadLocal Integer Integral Iterable Left Long Math Mutable None NotNull Number Numeric Object Option Ordered Ordering Override Package PartialFunction PartialOrdering PartiallyOrdered Predef Process ProcessBuilder ProcessEnvironment ProcessImpl Product Proxy Range RangeToString Readable Responder Right Runnable Runtime RuntimePermission ScalaObject SecurityManager SerialVersionUID Short Shutdown Some StackTraceElement StaticAnnotation StrictMath String StringBuffer StringBuilder StringCoding SuppressWarnings Symbol System SystemClassLoaderAction Terminator Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeConstraint UNIXProcess Unhashable UniquenessCache Void actors annotation ch cloneable collection com compat concurrent dbc deprecated inline instrument io java javax jline l management mobile native net noinline org package ref reflect remote runtime scala serializable settings specialized sun sunw swing testing text throws tools transient unchecked unsealed util volatile xml scala> l.zip zip zipAll zipWithIndex
Pretty neat. But that’s not all: the trunk REPL now contains a power user mode:
scala> :help All commands can be abbreviated - for example :h or :he instead of :help. :help prints this help message. :jar add a jar to the classpath. :load followed by a filename loads a Scala file. :power enable power user mode. :quit exits the interpreter. :replay resets execution and replays all previous commands. :silent disable/enable automatic printing of results. scala> :power ** Power User mode enabled - BEEP BOOP ** ** New vals! Try interpreter. ** ** New defs! Try mkType("T", "String") ** ** New cmds! :help to discover them ** :help prints this help message. :jar add a jar to the classpath. :load followed by a filename loads a Scala file. :power enable power user mode. :quit exits the interpreter. :replay resets execution and replays all previous commands. :silent disable/enable automatic printing of results. :dump displays a view of the interpreter's internal state. :tree displays ASTs for specified identifiers.
Let’s try some of this. Using mkType we can define a type alias, just as Haskell:
scala> mkType("IntList", "List[Int]") defined type alias IntList res3: scala.tools.nsc.InterpreterResults.Result = Success scala> val il: IntList = List("1","2","3") :5: error: type mismatch; found : java.lang.String("1") required: Int val il: IntList = List("1","2","3") scala> val il:IntList = List(1,2,3,4) il: IntList = List(1, 2, 3, 4) scala> il.foldLeft(0)(_+_) res5: Int = 10
Works like a charm.
Next, the :tree command displays some AST for classes and functions you’ve defined, if you’re interested in this:
scala> def sum(a: Int)(b: Int)(c: Int) = a + b + c sum: (a: Int)(b: Int)(c: Int)Int scala> :tree sum def sum(a: Int)(b: Int)(c: Int) = a.$plus(b).$plus(c) (DefDef)
Last but not least, we can make use of Scala’s new cutting edge features. For instance, Scala has limited support for applying tail call optimizations at compile time. Determining whether the compiler actually will perform this optimization can be a tricky business however. If you’re using this, but are unsure whether a function that you have written will actually be optimized by the compiler, you can use the @tailrec annotation now. For instance (the example is taken from the Programming in Scala book) the boom function shown below is not tail recursive, because of the increment function at the end. The bang function on the other hand is.
[scala]
import scala.annotation.tailrec
class Tails {
@tailrec def boom(x: Int): Int = {
if (x == 0) throw new Exception("boom!")
else boom(x-1)+ 1
}
@tailrec def bang(x: Int): Int = {
if (x == 0) throw new Exception("bang!")
else bang(x-1)
}
}
[/scala]
Using the @tailrec annotation on both, the compiler gives the following error:
:8: error: could not optimize @tailrec annotated method @tailrec def boom(x: Int): Int = { ^ :13: error: could not optimize @tailrec annotated method @tailrec def bang(x: Int): Int = {
A bit confusing at first sight, since we get compilation errors for both methods, while we were pretty sure the bang method should be tail recursive. However, this can be explained by the fact that the bang method is defined in a class as public and not final. This means it could be overridden in a subclass, thereby preventing tail call optimization. Subtleties like this really makes this annotation quite useful. If we change the class into an object (for which methods can’t be overridden), and remove the @tailrec annotation from the boom method, all compiles fine.
For more tail call and trampolining explorations in Scala, Rich Dougherty’s blog is a good place to start. Also check out the Scala’s TailRec class, which is available in the trunk.
I haven’t touched all of the new features that will be in Scala 2.8.0, but some a basic overview can be found here. Take a look and enjoy.