In the next posts, I'll be certainly assuming that you come to Scala from Java, however everybody is welcome!
I want first to demonstrate the use of a wonderful idea, borrowed from Haskell, the Option class. And this will be used with pattern matching. Said like that, this looks like advanced Computer Science stuff, but it's not.
This will help us manage something that plagues a lot of java applications: the dreaded NullPointerException.
An Option to parse options
Yes, I admit it. Parsing "options" on a command line is not the better way to introduce the "Option" class. So I'll try to go slow enough to reduce the confusion I have introduced in the first place,...
Anyway, this is a "real" example ("real" as "I'm using it", not as "the space shuttle runs with that"). I wrote a small utility in Scala to analyse the options passed on a command line. It can be used that way:
val action = getOptionValue("-action", "generateQuotes", args)
where
- "-action" is the name of the option on the command line: -action generateQuotes for instance
- generateQuotes is the default action if no "-action" option has been specified
- args is an array of string containing all the options passed on the command line (typically through the main static method)
How can I implement this getOptionValue method? Let's first provide a parseOption function taking only the name of the option and the arguments (args) as parameters. This function should return the value of the option if such a thing exists on the command line. What could be a Java implementation of that?
public String getOptionValue(String name, String defaultValue, Array[String] args) {
final String option = parseOption(name, args);
if (option == null)
return defaultValue;
else
return option;
}
public String parseOption(String name, Array[String] args) {
for (int i=0; i < args.length - 1; i++)
if (args[i].equalsIgnoreCase(name) && i < args.length - 1 && args[i+1] != null)
return args[i+1];
return null;
}
Please don't stare too much at the for loop, it is very naive but it was as YAGNI as I needed. Let's focus instead on the return value of the parseOption function. This function can either return a String or a null pointer meaning "I have not found an option value corresponding to the name you were asking for".Unfortunately, if I don't look at the parseOption code, I have no way to tell what the return value when the option is not found. Is it null, is it an empty string? Many times, in real life, this can be pretty much hard to tell, because the value comes from another function which comes from,... and so on.
So in presence of a large codebase, you may eventually add a superflous check:
if (option == null || option.equals("")) return defaultValue;
Better be safe than sorry,...
The Scala way
Now what does Scala offers in that situation?
The Option class provides 2 subclasses:
- None, which represents "I have found nothing"
- Some, which represents "I have found something, and you can get it"
def parseOption(name:String, args:Array[String]): Option[String] = {
for (i <- List.range(0, args.length - 1))
if (args(i).equalsIgnoreCase(name) && i < args.length -1 && args(i+1) != null)
return Some(args(i+1))
return None
}
And the getOptionValue function is defined with:
def getOptionValue(name:String, defaultValue:String, args:Array[String]): String = {
parseOption(name, args) match {
case None => defaultValue
case Some(value) => value
}
}
which reads like:
- parse the option with the name 'name'
- if you find nothing, return the defaultValue
- if you find some(thing), designated by 'value', return that value
In Scala, the "object match {case xxx => ...}" construct implements the "pattern matching" idea. Given an object, if its "structure" matches a given pattern, you can use parts of this object to do your job. In that case, I get an object 'Some', which was constructed from a 'value', so I can de-construct the object and access the value.
The death of the NPE?
I find this in itself quite useful and elegant, but this is not all! What if you forgot to add a None clause to your match construct? You get a compiler warning! So, not only you can infer from the parseOption signature that you will have to deal with None values, but you are even reminded to do so!
Now, if you add the Option class to the fact that Scala encourages you to declare final variables (with the "val" modifier, a bit shorter that "final" in Java) and forces you to assign a value to modifiable ones, you can really think twice when you have to write the word "null" in a Scala program.
And even more to the point
And there are other jewels, too. You can convey this idea of a "defaultValue" in an even more concise way. The Option class has a "getOrElse" method, so the code above can be rewritten as:
def getOptionValue(name:String, defaultValue:String, args:Array[String]) = {
parseOption(name, args).getOrElse(defaultValue)
}
You can also do it the other way around. You can define an action that will be done only if the value exists. The Option class implements Iterable, so you can write:
parseOption(name, args).foreach(Console.println _)
In that example foreach iterates on every value contained in the Option (designated by "_") and print it. So the value is printed only if it exists. Of course, here, I would really prefer a more meaningful term (such as "do") but you know what? There is also a technique that allow you to add methods to scala library classes, so it is possible to write that too! More on that later,...
References
To the interested reader, here are some more references:
- This is how David Pollak uses options, in the lift web framework, written in Scala
- The ancestor: Maybe in Haskell (you will also notice the presence of an Either structure which can be very convenient too)
Never ending debate
As a conclusion I will add another layer at the dynamic typing vs static typing debate. One of the goal I follow by using Scala is to experiment when and how static typing is better than dynamic typing. My current observations are:
- If you are to use static typing, let your compiler do a maximum of work for you. Emitting a warning if you forget an Option alternative looks like a good example to me, but there are some reaaaaally advanced used of typing (for software configuration or prevention of SQL injection attacks): “make illegal states impossible to represent” is certainly a good ideal in those cases
- Sometimes it can be irritating to get the types right, and you have to be quite aware about things such as co-variance, contra-variance, dependent types and all the folklore. I will certainly write a post about that to give another "real-life" example, otherwise it can look pretty esoteric. The way I see it is that static typing is a way to encode some business constraints so that they can be checked before you even run the program. Sometimes it is possible and clean, sometimes the translation from your domain constraints to type constraints is a bit clumsy but it works, sometimes it is downright impossible and you need some "programming"
- There are some things I can do with dynamic typing that I can not do with static typing, such as generating some classes with repetitive attributes and methods, depending on the content of a file. The dynamic solution is very concise and provide "just good enough" objects