If Java is a get shit done™ language then Scala is a get shit done fast™ language. The reason is that the signal to noise ratio is very high and that concise code is easier to reason about which makes it less error prone.
When understanding Scala it is sometimes important to have a look at some real-world examples — especially when it comes to functional programming. When I first started using Scala I had a hard time understanding some of the constructs used by other people. In this blog post I want do explain some of them. The first example is a classic one. You want to debug some structure and output all elements which have a condition set.
Printing Elements of a Collection
You will find this in every first-steps guide for Scala. What I like about it is the fact that you have only little amount of code to write if you want to inspect a certain structure.
list filter { _.condition } foreach println
Flattening a Collection
Convert a collection of collections to a flat collection. For instance you have a List<List<String>> in Java and want to get a List<String> instead.
final List<String> newList = new LinkedList<String>();
for(final List<String> innerList : oldList) {
newList.addAll(innerList);
}
A lot of code for a common operation. Probably the reason why the Google Guava library supports Iterables.concat(oldList) to get the same result. So how is this done in Scala?
val newList = oldList.flatten
A simple operation which is very powerful. Let me give you another example. Suppose you have a List[Option[T]] for some T. Before understanding Scala very well and before reading this excellent article about monads I once wrote shameful code like this.
val listOfOptions: List[Option[Int]] =
List(Some(1), None, Some(3), None, Some(5))
// don't do this!
val listOfDefinedValues: List[Int] =
listOfOptions filter { _.isDefined } map { _.get }
println(listOfDefinedValues) //List(1, 3, 5)
When encountering such a construct an alarm should raise in your head, telling you there must be a better way to do this. flatten to the rescue. println(listOfOptions.flatten) would do the job.
val listOfOptions: List[Option[Int]] = List(Some(1), None, None, Some(2)) println(listOfOptions.flatten) //List(1, 2)
Using flatMap for good
Although flatMap might sound esoteric at first it is a very helpful operation. So first of all you know there is the map operation which is extremely useful in a sense like this.
"hello world" map { _.toUpper }
So what is the deal with flatMap? It would not work in this case and the compiler would raise a type error. That is because a collection is expected as the result.
flatMap makes a lot of sense when you have a collection and need to pass every element into a function that would then again return a collection.
val list = List(1,2,3,4) def intToListOfChars(i: Int) = i.toString.toList val listOfListOfChar: List[List[Char]] = list map intToListOfChars
As you can see calling map with intToListOfChars creates a list of lists and we could later flatten that list agin. Or we simply use flatMap which does all of this in one step.
val listOfChars = list flatMap intToListOfChars
Still this example is pretty awkward. But imagine you have a method like trimToOption(value: String): Option[String] which trims a string and returns None if the string is empty or null (for whatever reason).
If you write a webapplication which expects user input you are probably working with a lot of Option[T] values (or Box[T] in case of Lift). For example like this:
val description: Option[String] = extractDescription(request)
Now you also want to trim that description. Here are a couple of ways on how to do it using trimToOption.
// not so nice
val trimmed1 = trimToOption(description getOrElse "")
// ugly as hell
val trimmed2 = description match {
case Some(desc) => trimToOption(desc)
case None => None
}
// kind of ugly
val trimmed3 = (description map trimToOption).flatten
// yey! :)
val trimmed4 = description flatMap trimToOption
So you see that flatMap is actually a pretty usable function.
Passing a Tuple to a Function
Sometimes you have to pass a tuple to a function and instead of the tuple you would like to pass each value of the tuple as a parameter.
val listOfStrings =
List("Hello", "World")
val withIndex = listOfStrings.zipWithIndex
println(withIndex) //List((Hello,0), (World,1)))
So now imagine you have a method like this:
def printStringWithIndex(value: String, index: Int) = println(index+": "+value)
In this case withIndex foreach printStringWithIndex would not work because we pass a tuple to that method. Scala has a nice built-in method called tupled which takes a function and converts it to one accepting a tuple.
withIndex foreach (printStringWithIndex _).tupled
Using the Compiler
Imagine you have a class with a type parameter like CustomList[T] and now you want to have a method which is implemented only for some kind of T. Obviously one way would be to use traits and mix them in on demand. But it gets more and more complex.
Fortunately we can let the compiler proof the evidence of a type at a position in our code.
class Foo[T](bar: T) {
// only allow this method if T is a String
def length()(implicit proof: T =:= String) =
bar.length
}
val foo1 = new Foo(123) //works just fine
foo1.length //does not compile.
val foo2 = new Foo("hello")
foo2.length //5
This is great because it allows us to introduce phantom types and to write less code for certain structures. For instance we have a Connector[A] in Audiotool. This connector accepts connections to other connectors. It is either an input or an output and it transmits an abstract signal. You can only connect an output with an input. A Connector accepts only connections of its own type and virtual connections. This means the following example would be uber-cool code. Unfortunately we did not write Audiotool in Scala ;)
class Connector[A] {
add[B](conn: Conn[A, B])(implicit proof: A =:= B)
{ ... }
add(connection: VirtualConn[_, _])
{ ... }
}
Note: This is in fact not a real example since it would not work. You could only connect an output with another output but I think you might get the idea and it is easier to follow.
Conclusion
Functional programming might seem weird. Constructs like =:= or flatMap are awkward at first but make complete sense. Using Scala and picking some of the functional idioms which you think are right and well understood in your team can increase productivity and lead to less bugs.
Here is an example of why a guard is important and how all this stuff works. You have two Audiotool instances running that share the same state. We call them A and B. A and B are connected via P2P no an arbitrary network. This means exchanging messages introduces latency and we cannot guarantee that A is at the same state of B.