Sunday, January 9, 2011

Scala building blocks

When I first started reading about Scala, I saw a lot of very interesting looking code, but I felt lost.  It looked as if I was trying to learn a foreign language with a phrasebook.  I saw a number of examples, but I felt I was missing the building blocks of the language.

I've been using Scala for a little while now, and this is the introduction I wish I'd had then.  This post is not meant as a detailed "how things are" but just to provide a newcomer with the right frame of mind to understand the rest.  There are some over-simplifications, but I find they help me understand, so I'm sharing them.


Classes, traits, objects, companions

Everything in Scala is an object, and every object is of a particular class.  Just like Java (if you ignore the pesky primitives).  So, generally speaking, classes work the way you expect them.  Except that there's no "static".  No static method, no static values.

Traits are like interfaces, but they can include fields and method implementations.

Objects can be declared and are then known throughout the program, in a way that's reminiscent of dependency injection, particularly the Grails flavour.  Object declarations can even include implementation, and new fields and methods.

Companion objects are objects named the same as a class.  This is how you handle details that you would normally want to do as static methods/fields/etc.  This is very elegant: it makes it look as if you had access to static elements, while preserving a true object-oriented approach.

So you may encounter code such as

class Car extends Vehicle with PassengerAble {
   val numWheels = 4
   val numPassengerSeats = 6
}

object Car {
  def findByLicensePlate(String plate, String emitter) : Car = {
    Authorities.findEmittingAuthority(emitter).findByLicensePlate(plate);
  }
}

object MyOwnCar extends Car with PassengerSideAirbags, RemoteStarter {
  val numChildSeats = 2;
  def honk : Unit = {
    HonkSounds.dixie.play;
  }
}

In that example, the first Car is a class.  The second Car is an object.  MyOwnCar is an object that can be addressed anywhere (same package rules apply as java), but MyOwnCar has extra stuff in it: PassengerSideAirbags and RemoteStarter are trait (you can guess that because of the with keyword).  It even defines a new method so that honking it MyOwnCar should remind you of the Dukes of Hazzard.


Types

Unlike Java, in Scala, everything is an object.  There is no such thing as a primitive.


Basic types

At the top of the object hierarchy, we have Any.  Every object, either what you think of an object in a Java sense, or the types that are primitives in Java, every thing inherits from Any.

The hierarchy then splits into two: AnyVal and AnyRef.  Primitive-like types are under AnyVal and AnyRef is essentially the equivalent of java.lang.Object.  All Java and Scala classes that you define will be under AnyRef.

Strings are just like Java Strings.  Double-quoted.  But Scala defines a few additional methods on them, and treats them as collections, too, so your String is also a list of characters.  A particularly convenient method, I've found, is toInt.  There's toDouble and toBoolean too.

Unit is what a method returns when it doesn't return anything.  You can think of it as "void".

Null as you know it is a value, but in Scala, every value has type, so it's of type Null.  And Null extends every single class, so that null (the one and only instance of Null) can be assigned to any AnyRef object.  It sounds crazy, but if you let it sink in, it will make sense.  Null is actually a trait, not a class.

Nothing is the absolute bottom of the hierarchy.  Nothing doesn't have any instances.


Numeric Types

Integers are of type Int.

Doubles are Doubles, floats are Float.

A litteral in the code is treated an object of the appropriate type.  Things just work, without "autoboxing" or other convolutions.

Strings and numeric types are immutable.


Collections

Collections come in mutable and immutable variations.

The operation you can perform on collections are probably easier to understand and get used to when you remember lisp/scheme.  Or perl.  Try to view collections are a whole, and not so much as an object with a few methods, the way we tend to in Java.  The think of the methods as operations on the whole.  It may also help to dust out set theory concepts.

A special kind of collection is the Tuple.   It's an ordered group of N items that can be of the same or of different types.  They are defined for N=2 to N=22.  You can access the j-th element of the tuple with ._j   (ex: the first is myTuple._1, the third is myTuple._3)

Options

The first thing you have to realize about options is that they're everywhere, so you better get used to the idea.  Lift uses Box instead, but it serves the same purpose.

The second thing you have to realize is that Options (or Box) is the right way to handle multiple scenarios, but you've spent your programming life working around that fact.

Let's take a simple example.  You want to implement a method that computes the square root of the parameter it receives.  What should that method return?  a Double.  So you start:

def sqrt (x: Double) : Double = {
 // ...
}

You start implementing, maybe by writing some tests first... Hm... what happens if x is a negative number?  you may think "I'll just return null" or "I'll throw an exception".  If you return null, chances are the caller wont bother checking, and bad things will happen.  If you throw an exception, you slow things down a bit (exceptions should be for exceptional condition).  If you throw an unchecked exception, who knows where in the caller's code that will be handled.  If you throw a checked exception, the caller's probably already sick of catching every exception every where and has a standard, no-thought solution (ignore, wrap in a RuntimeException, etc)

Options tell you that there are multiple outcomes possible.  The case where a regular value is returned, the Option is actually a Some (subclass of Option), with the value in it.  In cases when no value can be returned, the Option is actually None (an object).  Of course, Options can be used in contexts other than returning a value from a method, but that's an easy way to see their usefulness.

Once you have an Option, you can test it for Some or None, or you can try to find what's in it: myOption.get will return the content of the Some (or throw an exception if it's None, so don't do that unless you know).  If you're not sure, you can use myOption.getOrElse(defaultVal)  which will return the contents of the Some, or gracefully (and elegantly) use the default you provided.

Or, you can use matching to decide what to do:

MathHelper.sqrt(x) match {
  case Some(y) => "The square root of " + x + " is " + y
  case None => "The value " + x + " does not have a square root"
}

No comments:

Post a Comment