When I tell people I write code in Scala, a typical question is well, why? When it comes to writing code, most of my work is straightforward: SQL database on the backend, some architectural glue, CRUD, some exception handling, transactions handlers and an HTML or JSON front end. The tools have changed, but the problems are usually the same: you could get a website up in 5 minutes with Rails or Dropwizard. So why pick Scala?
It's a tough question to answer off the bat. If I point to the language features, it doesn't get the experience across. It's like explaining why I like English by reading from a grammar book. I don't like Scala because of its functional aspects or its higher kinded type system. I like Scala because it solves practical, real world problems for me.
You can think of Scala as Java with all the rough edges filed off, with new features that make it easier to write correct code and harder to create bugs. Scala is not a purist's language – it goes out of its way to make it easy for Java programmers to dip their toes in the pool. You can literally take your Java code and hit a key to create working Scala code.
So what problems does Scala solve?
Let's start with the single biggest problem in programming, the design flaw that's caused more errors than anything else combined. Null references.
Solving for Null
Scala avoids null pointer references by providing a special type called Option. Methods that return Option[A] (where A is the type that you want, i.e. Option[String]) will give you an object that is either a wrapper object called 'Some' around your type, or None. There are a number of different ways you can use Option, but I'll just mention the ones I use most. You can chain Options together in Scala using for comprehensions:
for {
foo <- request.params('foo')
bar <- request.params('bar')
} yield myService.process(foo, bar)
or through a map:
request.params('foo').map { foo => logger.debug(foo) }
or through pattern matching.
request.params('foo') match {
case Some(foo) => { logger.debug(foo) }
case None => { logger.debug('no foo :-(') }
}
Not only is this easy, but it's also safer. You can flirt with NPE saying myOption.get, but if you do that, you deserve what you get. Not having to deal with NPE is a pleasure.
Right Type in the Right Place
What's the second biggest problem in programming? It's a huge issue in security and in proving program correctness: invalid, unchecked input.
Take the humble String. The work of manipulating strings is one of the biggest hairballs in programming – they're pulled in from the environment or embedded in the code itself, and then programs try to figure out how best to deal with them. In one case, a string is displayed to the user and it's done. In another case, an SQL query is embedded as a query parameter on a web page and passed straight through to the database. To the compiler, they're just strings and there is no difference between them. But there are some types of strings that are suitable to pass to databases, and some which are not. Ideally, we'd like to tell the compiler that SQL and query parameters have different types. Scala makes this easy.
With the Shapeless library, you can add distinguishing type information to objects and ensure that you can't pass random input in:
import shapeless.TypeOperators._
type SqlString = Newtype[String, Any]
val x: SqlString = newtype("SELECT * FROM USER")
I've called out strings because it's a good example, but you can also do this for repository IDs. No more this:
case class User(id: Int, firstName:String)
def lookup(id:Int) : User
When you can have this:
case class User(id: Id[User], firstName:String)
def lookup(id:Id[User]) : User
You can also use this to validate input on the front end. One of the big problems with regular expressions is that when you parse a random string for certain kinds of input, you get back… more strings. You may be validating a string as a username (no spaces, no odd characters), but what you've got at the end is a string that says it's a username.
val rawInput = request.params('foo')
if (isUsername(rawInput)) {
val username = rawInput
}
You can replace that with something nicer.
val email : Option[Username] = parseUsername(rawInput)
This embeds the constraint in the type itself. You can design your API to accept Username instead of String, and so enforce a kind of whitelisting.
Can you do this in Java? Yes, but it's inconvenient. Scala's type system makes it easy for you, and in 2.10 there will be Value Classes, which will provide this functionality in the core language itself.
Doing the gruntwork for you
The previous example can be improved though. Really, we just want a Username at the end – we don't want to have to call parseUsername on it. Fortunately, Scala rewards the lazy with implicit conversions. If you define a method like this and use the implicit keyword:
implicit def string2username(input : String) : Option[Username] = parseUsername(input)
And do this:
val email : Option[Username] = rawInput;
Then the compiler is smart enough to see that a String isn't an Option[Username], and looks through any implicit methods available to do the conversion.
There is an element of 'magic' to implicit conversions, especially when you're reading someone else's code and trying to figure out where the conversion is happening. You can find the appropriate implicit through the REPL, or through IDEA.
Providing Context
There are many cases in programming where everything depends on a Context object in some way: either you're using a database connection, or you rely on a security principal, or you're resolving objects from a request or JAAS / LDAP / Spring context… the list goes on. Whatever it is, it's passed in by the system, it's absolutely essential, and you can count on most of your API to depend on it in some way. A typical Java way to deal with this is to make it part of the parameter list, or try to ignore it and make it a ThreadLocal object.
public void doStuff(Context context);
Scala has a better way to deal with this: you can specify implicit parameters on a method.
def doStuff(implicit context:Context)
which means that anything marked as implicit that is in scope will be applied:
implicit val context = new Context()
doStuff // uses val context automatically.
This is all handled by the compiler: just set up the implicits and Scala will do the rest.
A place for everything
So now you have a number of implicit methods, value classes and type definitions and wotnot. In Scala, there's a place to keep all this stuff that is so intuitive, you may not think of it as a place at all. It's the package object.
Package objects are supremely useful. You define a file called package.scala, then in the file you put
package object mypackagename {
implicit def string2username(input : String) : Option[Username] = parseUsername(input)
}
and after that point, anything with 'import mypackagename._' will import the package object as well. One less thing to think about.
Free Data Transfer Objects
Case classes. So called because they're used in case statements (see below).
case class Data(propertyOne:String, propertyTwo:Int)
Immutable, convenient, and packed with functionality. They make creating data types or DTOs trivial. They're cool.
Free Range (Organic) Checking
Scala contains a powerful pattern matching feature. You can think of it as a switch statement on steroids.
a match {
case Something => doThis
case SomethingElse => doThat
}
There are so many things that feed into pattern matching – extractor objects, aliases, matching on types, regular expressions and wildcards – it's the 'regexp' of Scala. It takes in an object as input, filters it, and manipulates it in exactly the way you want.
But the thing I really like about pattern matching is what it doesn't let you do. It doesn't let you miss something.
There's a feature called sealed classes which lets you define all the valid types in a file. If you define a trait with the sealed keyword inside a file, then any classes you define inside that file that extend that trait are the ONLY classes that will extend that trait.
sealed trait Message { def msg: String }
case class Success(msg:String) extends Message
case class Failure(msg:String) extends Message
The compiler knows this, and so when you write use pattern matching against that trait, it knows that it must be one of the case classes defined. If not all of the case classes are defined in the match, it will print out a warning method saying that you don't have an exhaustive match.
def log(msg: Message) = msg match {
case Success(str) => println("Success: " + str)
case Failure(str) => println("Failure: " + str)
}
And More
But that's enough for now. I hope this gives you an idea of why I like Scala. If you have any features dear to your heart, add them to the comments and let me know what makes you happy.
Comments