I highly recommend a careful reading of "Programming in Scala" by Martin Ordesky, Lex Spoon, and Bill Venners. Read it through once to get familiar with the syntax and features, and then read it again to see how it all fits together.
I find it worth emphasizing how some features should change the way a Java programmer designs and builds programs.
I will assume functional features are not a novelty at this point. We have had Scheme ( http://mitpress.mit.edu/sicp/full-text/book/book.html ) and Haskell ( http://www.realworldhaskell.org/ ) for a long time to show us how functional programming is done. Scala is not a pure functional language, and that should be accepted as a feature, not as a compromise or defect. Object-oriented encapsulation of state has proven useful a long time, and we know how to scale large projects with this model. We can see how to move our projects and programmers from Java to Scala.
I'll also assume that you've had time to get accustomed to some of the syntactic sugar of scala. Conciseness does not necessarily change how you program, though it can when boilerplate was previously overwhelming.
At runtime, you can assemble a chuck of code, often called a "thunk," and pass it along to be evaluated when and if necessary.
Scala supports strict evaluation of method arguments: any expressions are evaluated before they are passed. Nevertheless, you can easily prevent evaluation by passing a function that contains the expressions you want evaluated.
A good example is an assert, where you only want to evaluate an expression when asserts are enabled. Java found it necessary to introduce a new keyword to support this feature. Scala does not.
This function always evaluates the argument, even when disabled:
val assertionsEnabled = false def dumbAssert(test: Boolean) = if (assertionsEnabled && !test) throw new AssertionError dumbAssert(1>2) |
Notice that you can assign a function literal to a function value:
scala> val calculateTest = () => {println("calculating..."); 1 > 2} calculateTest: () => Boolean = <function0> scala> calculateTest() calculating... res0: Boolean = false scala> dumbAssert(calculateTest()) calculating |
This function retains an expression, then evaluates that expression when the function is called, even when assertions are not enabled.
(Note this function literal is actually an anonymous object that extends the trait Function0. An apply method invokes the function.)
Instead you can define an assert that takes a function.
def betterAssert(test: () => Boolean) = if (assertionsEnabled && !test()) throw new AssertionError betterAssert(() => 2>1) betterAssert(calculateTest) |
The function is called only when assertions are enabled.
Passing a function is still a burden. If you try a simple expression evaluating to a boolean, you get a error because a function is expected.
scala> betterAssert(2>1) <console>:8: error: type mismatch; found : Boolean(true) required: () => Boolean betterAssert(2>1) |
If you omit the () from the declaration, then you are passing the argument by name, and delaying its evaluation until first used.
def bestAssert(test: => Boolean) = if (assertionsEnabled && !test) throw new AssertionError bestAssert(2>1) bestAssert(calculateTest()) |
You no longer see the calculation as a side-effect, when the expression does not need to be evaluated.
Notice how similar this syntax is to forming and passing a function.
Ever find yourself copying boilerplate that you seem unable to reuse? Let's pretend the following try/catch/finally block is useful.
def mathTest(test: => Boolean): Unit = { try { if (!test) throw new AssertionError("Test failed"); } catch { case ae: ArithmeticException => throw new AssertionError("Bad Math"); } finally { println("Finally") } } |
It would behave like this:
scala> mathTest (3>2) Finally scala> mathTest (2>3) Finally java.lang.AssertionError: Test failed ... scala> mathTest (3/0>2) Finally java.lang.AssertionError: Bad Math ... |
Let's pretend you want to change the error words, and the contents of the finally.
def mathTest2(finalAction: => Unit, errorWords: String, test: => Boolean): Unit = { try { if (!test) throw new AssertionError(errorWords) } catch { case ae: ArithmeticException => throw new AssertionError("Bad math") } finally { finalAction } } |
You could use it like this, awkwardly:
scala> mathTest2(println("Pretend I'm closing a file"),"normal math", 3>2) Pretend I'm closing a file scala> mathTest2(println("Pretend I'm closing a file"),"crazy math", 1/0>2) Pretend I'm closing a file java.lang.AssertionError: Bad math ... |
Instead, you can declare a function that returns a function that returns a function. (And the first argument is a function.)
def mathTest3(finalAction: ()=>Unit)(errorWords: String)(test: => Boolean): Unit = { try { if (!test) throw new AssertionError(errorWords) } catch { case ae: ArithmeticException => throw new AssertionError("Bad math") } finally { finalAction() } } mathTest3: (finalAction: () => Unit)(errorWords: String)(test: => Boolean)Unit |
You can then save a new "curried" function like this:
val mathTest4 = mathTest3 { () => println("Never again forgetting to close that file.") } _ |
The new function mathTest4
is a function that returns a
function.
Why did I not pass this first argument by name, instead of as a function? Because that argument is evaluated to construct the function that is returned.
You can apply this as you would a control structure:
mathTest4 ("Normal math?") { val two = 2 val three = 3 println ("Finished with expensive calculations."); three > two } Finished with expensive calculations. Never again forgetting to close that file. |
Or perhaps not so good math:
mathTest4 ("Abnormal math?") { val one = 1 val infinity = 1/0 println ("Did I get away with dividing by zero?"); infinity > one } Never again forgetting to close that file. java.lang.AssertionError: Bad math ... |
In Java, we have null
and void
. In Scala we have a few
more alternatives.
In Scala null
is actually an instance of the Null
class. The Null
class is a subclass of any class that
derives from AnyRef
. So you can assign a null instance to
any reference type:
scala> val x = null x: Null = null scala> var y = "foo" y: java.lang.String = foo scala> y = x y: java.lang.String = null scala> null == y res4: Boolean = true |
The ==
is a final method defined in Any
Unit
is like a void
, returned from a method that is
called only for side-effects. However Unit
is an actual
type, with only one allowed value, written as ()
.
scala> Unit res3: Unit.type = object scala.Unit scala> val unit = () unit: Unit = () scala> def foo() = println("bar") foo: ()Unit scala> assert (foo() == ()) bar |
Nil
is a singleton object that corresponds to the empty
list:
scala> Nil res2: scala.collection.immutable.Nil.type = List() scala> val list = List("a") list: List[java.lang.String] = List(a) scala> list.tail res29: List[java.lang.String] = List() scala> assert (list.tail == Nil) |
Nothing
is the value returned by a method that does not
return. You can use such a method where another value is
expected.
scala> def f() = {throw new Exception("No return.")} f: () Nothing scala> var g = () => {"Returns string"} g: () => java.lang.String = <function0> scala> g = f g: () => java.lang.String = <function0> scala> g() java.lang.Exception: No return. |
None
is one of two possible values for the Option
type.
The other possible value is Some(x)
, where x is a useful
value.
Pattern-matching in scala, is actually an object-oriented
implementation of algebraic types. Option
is an example.
For example, we can define a type like this
abstract class Color case class Black() extends Color case class White() extends Color case class Gray(shade: Int) extends Color case class RGB(r: Int, g: Int, b: Int) extends Color |
This declares an abstract base type, and four specific derived types. These are types, not instances, like an enum.
Each case class automatically gets some functionality:
scala> val b = Black() b: Black = Black() scala> val c: Color = Black() c: Color = Black() scala> val g = Gray(25) g: Gray = Gray(25) |
val
fields.
scala> val g = Gray(25) g: Gray = Gray(25) scala> g.shade res0: Int = 25 scala> g.shade = 32 <console>:9: error: reassignment to val g.shade = 32 |
equals
,
hashcode
, and toString
scala> g res1: Gray = Gray(25) scala> g == new Gray(25) res2: Boolean = true |
In combination, this gives great power to a match expression, which we here put into a function that returns a String.
def describe(c: Color) = c match { case Gray(25) => "favorite color" case Gray(s) if s>255 => throw new IllegalArgumentException case Gray(s) => "shade of "+s case rgb @ RGB(0,_,_) => "No red in "+rgb case other => "Something else: "+other } |
This is better than a overloaded function, because you can make cases for specific values of arguments.
scala> describe (Gray(25)) res5: java.lang.String = favorite color scala> describe(Gray(16)) res6: java.lang.String = shade of 16 scala> describe (RGB(0,1,1)) res7: java.lang.String = No red in RGB(0,1,1) scala> describe (RGB(1,1,1)) res8: java.lang.String = Something else: RGB(1,1,1) scala> describe (Black()) res9: java.lang.String = Something else: Black() scala> describe (Gray(256)) java.lang.IllegalArgumentException ... |
You can leave off the match
because a case expression is a
function literal:
val describe: Color => String = { case Gray(25) => "favorite color" case Gray(s) if s>255 => throw new IllegalArgumentException case Gray(s) => "shade of "+s case rgb @ RGB(0,_,_) => "No red in "+rgb case other => "Something else: "+other } |
Option[A]
and List[A]
are parametric versions of case
classes.
In this way you can create nested cases.
scala> val test = Option(Gray(25)) test: Option[Gray] = Some(Gray(25)) scala> val list = List(Some(Gray(25)), None, Some(Gray(1))) list: List[Option[Gray]] = List(Some(Gray(25)), None, Some(Gray(1))) |
Since the outer List
is a sequence, we can iterate over it
with pattern matching:
scala> for (Some(c) <- list) {println(c)} Gray(25) Gray(1) |
This is actually equivalent to
list filter { case Some(c) => true case _ => false } foreach { case Some(c) => println(c) } |
Or better, iterate with our describe function:
scala> for (Some(c) <- list) yield describe(c) res6: List[String] = List(favorite color, shade of 1) |
Which is equivalent to
list filter { case Some(c) => true case _ => false } map { case Some(c) => describe(c) } |
Even better, let's do it the recursive way.
def print(list: List[Option[Color]]): Unit = list match { case Some(c) :: rest => println (describe(c)) ; print(rest) case None :: rest => print(rest) case Nil => () } |
This works because ::
(pronounced "cons") and Nil
are
the two case classes for a List.
scala> print (list) favorite color shade of 1 |
Here's the shorter version using a function literal:
val print: List[Option[Color]] => Unit = { case Some(c) :: rest => println (describe(c)) ; print(rest) case None :: rest => print(rest) case Nil => () } |
Return to parent directory.