The Seductions of Scala, Part III - Concurrent Programming 392
This is my third and last blog entry on The Seductions of Scala, where we’ll look at concurrency using Actors
and draw some final conclusions.
Writing Robust, Concurrent Programs with Scala
The most commonly used model of concurrency in imperative languages (and databases) uses shared, mutable state with access synchronization. (Recall that synchronization isn’t necessary for reading immutable objects.)
However, it’s widely known that this kind of concurrency programming is very difficult to do properly and few programmers are skilled enough to write such programs.
Because pure functional languages have no side effects and no shared, mutable state, there is nothing to synchronize. This is the main reason for the resurgent interest in function programming recently, as a potential solution to the so-called multicore problem.
Instead, most functional languages, in particular, Erlang and Scala, use the Actor model of concurrency, where autonomous “objects” run in separate processes or threads and they pass messages back and forth to communicate. The simplicity of the Actor model makes it far easier to create robust programs. Erlang processes are so lightweight that it is common for server-side applications to have thousands of communicating processes.
Actors in Scala
Let’s finish our survey of Scala with an example using Scala’s Actors library.
Here’s a simple Actor that just counts to 10, printing each number, one per second.
import scala.actors._
object CountingActor extends Actor {
def act() {
for (i <- 1 to 10) {
println("Number: "+i)
Thread.sleep(1000)
}
}
}
CountingActor.start()
The last line starts the actor, which implicitly invokes the act
method. This actor does not respond to any messages from other actors.
Here is an actor that responds to messages, echoing the message it receives.
import scala.actors.Actor._
val echoActor = actor {
while (true) {
receive {
case msg => println("received: "+msg)
}
}
}
echoActor ! "hello"
echoActor ! "world!"
In this case, we do the equivalent of a Java “static import” of the methods on Actor
, e.g., actor
. Also, we don’t actually need a special class, we can just create an object with the desired behavior. This object has an infinite loop that effectively blocks while waiting for an incoming message. The receive
method gets a block that is a match statement, which matches on anything received and prints it out.
Messages are sent using the target_actor ! message
syntax.
As a final example, let’s do something non-trivial; a contrived network node monitor.
import scala.actors._
import scala.actors.Actor._
import java.net.InetAddress
import java.io.IOException
case class NodeStatusRequest(address: InetAddress, respondTo: Actor)
sealed abstract class NodeStatus
case class Available(address: InetAddress) extends NodeStatus
case class Unresponsive(address: InetAddress, reason: Option[String]) extends NodeStatus
object NetworkMonitor extends Actor {
def act() {
loop {
react { // Like receive, but uses thread polling for efficiency.
case NodeStatusRequest(address, actor) =>
actor ! checkNodeStatus(address)
case "EXIT" => exit()
}
}
}
val timeoutInMillis = 1000;
def checkNodeStatus(address: InetAddress) = {
try {
if (address.isReachable(timeoutInMillis))
Available(address)
else
Unresponsive(address, None)
} catch {
case ex: IOException =>
Unresponsive(address, Some("IOException thrown: "+ex.getMessage()))
}
}
}
// Try it out:
val allOnes = Array(1, 1, 1, 1).map(_.toByte)
NetworkMonitor.start()
NetworkMonitor ! NodeStatusRequest(InetAddress.getByName("www.scala-lang.org"), self)
NetworkMonitor ! NodeStatusRequest(InetAddress.getByAddress("localhost", allOnes), self)
NetworkMonitor ! NodeStatusRequest(InetAddress.getByName("objectmentor.com"), self)
NetworkMonitor ! "EXIT"
self ! "No one expects the Spanish Inquisition!!"
def handleNodeStatusResponse(response: NodeStatus) = response match {
// Sealed classes help here
case Available(address) =>
println("Node "+address+" is alive.")
case Unresponsive(address, None) =>
println("Node "+address+" is unavailable. Reason: <unknown>")
case Unresponsive(address, Some(reason)) =>
println("Node "+address+" is unavailable. Reason: "+reason)
}
for (i <- 1 to 4) self.receive { // Sealed classes don't help here
case (response: NodeStatus) => handleNodeStatusResponse(response)
case unexpected => println("Unexpected response: "+unexpected)
}
We begin by importing the Actor
classes, the methods on Actor
, like actor
, and a few Java classes we need.
Next we define a sealed abstract base class. The sealed
keyword tells the compiler that the only subclasses will be defined in this file. This is useful for the case statements that use them. The compiler will know that it doesn’t have to worry about potential cases that aren’t covered, if new NodeStatus
subclasses are created. Otherwise, we would have to add a default case clause (e.g., case _ => ...
) to prevent warnings (and possible errors!) about not matching an input. Sealed class hierarchies are a useful feature for robustness (but watch for potential Open/Closed Principle violations!).
The sealed class hierarchy encapsulates all the possible node status values (somewhat contrived for the example). The node is either Available
or Unresponsive
. If Unresponsive
, an optional reason
message is returned.
Note that we only get the benefit of sealed classes here because we match on them in the handleNodeStatusResponse
message, which requires a response
argument of type NodeStatus
. In contrast, the receive
method effectively takes an Any
argument, so sealed classes don’t help on the line with the comment “Sealed classes don’t help here”. In that case, we really need a default, the case unexpected => ...
clause. (I added the message self ! "No one expects the Spanish Inquisition!!"
to test this default handler.)
In the first draft of this blog post, I didn’t know these details about sealed classes. I used a simpler implementation that couldn’t benefit from sealed classes. Thanks to the first commenter, LaLit Pant, who corrected my mistake!
The NetworkMonitor
loops, waiting for a NodeStatusRequest
or the special string “EXIT”, which tells it to quit. Note that the actor sending the request passes itself, so the monitor can reply to it.
The checkNodeStatus
attempts to contact the node, with a 1 second timeout. It returns an appropriate NodeStatus
.
Then we try it out with three addresses. Note that we pass self
as the requesting actor. This is an Actor
wrapping the current thread, imported from Actor
. It is analogous to Java’s Thread.currentThread()
.
Curiously enough, when I run this code, I get the following results.
Unexpected response: No one expects the Spanish Inquisition!!
Node www.scala-lang.org/128.178.154.102 is unavailable. Reason: <unknown>
Node localhost/1.1.1.1 is unavailable. Reason: <unknown>
Node objectmentor.com/206.191.6.12 is alive.
The message about the Spanish Inquisition was sent last, but processed first, probably because self
sent it to itself.
I’m not sure why www.scala-lang.org couldn’t be reached. A longer timeout didn’t help. According to the Javadocs for InetAddress.isReachable), it uses ICMP ECHO REQUESTs if the privilege can be obtained, otherwise it tries to establish a TCP connection on port 7 (Echo) of the destination host. Perhaps neither is supported on the scala-lang.org site.
Conclusions
Here are some concluding observations about Scala vis-à-vis Java and other options.
A Better Java
Ignoring the functional programming aspects for a moment, I think Scala improves on Java in a number of very useful ways, including:
- A more succinct syntax. There’s far less boilerplate, like for fields and their accessors. Type inference and optional semicolons, curly braces, etc. also reduce “noise”.
- A true mixin model. The addition of traits solves the problem of not having a good DRY way to mix in additional functionality declared by Java interfaces.
- More flexible method names and invocation syntax. Java took away operator overloading; Scala gives it back, as well as other benefits of using non-alphanumeric characters in method names. (Ruby programmers enjoy writing
list.empty?
, for example.) - Tuples. A personal favorite, I’ve always wanted the ability to return multiple values from a method, without having to create an ad hoc class to hold the values.
- Better separation of mutable vs. immutable objects. While Java provides some ability to make objects
final
, Scala makes the distinction between mutability and immutability more explicit and encourages the latter as a more robust programming style. - First-class functions and closures. Okay, these last two points are really about FP, but they sure help in OO code, too!
- Better mechanisms for avoiding
null
’s. TheOption
type makes code more robust than allowingnull
values. - Interoperability with Java libraries. Scala compiles to byte code so adding Scala code to existing Java applications is about as seamless as possible.
So, even if you don’t believe in FP, you will gain a lot just by using Scala as a better Java.
Functional Programming
But, you shouldn’t ignore the benefits of FP!
- Better robustness. Not only for concurrent programs, but using immutable objects (a.k.a. value objects) reduces the potential for bugs.
- A workable concurrency model. I use the term workable because so few developers can write robust concurrent code using the synchronization on shared state model. Even for those of you who can, why bother when Actors are so much easier??
- Reduced code complexity. Functional code tends to be very succinct. I can’t overestimate the importance of rooting out all accidental complexity in your code base. Excess complexity is one of the most pervasive detriments to productivity and morale that I see in my clients’ code bases!
- First-class functions and closures. Composition and succinct code are much easier with first-class functions.
- Pattern matching. FP-style pattern matching makes “routing” of messages and delegation much easier.
Of course, you can mimic some of these features in pure Java and I encourage you to do so if you aren’t using Scala.
Static vs. Dynamic Typing
The debate on the relative merits of static vs. dynamic typing is outside our scope, but I will make a few personal observations.
I’ve been a dedicated Rubyist for a while. It is hard to deny the way that dynamic typing simplifies code and as I said in the previous section, I take code complexity very seriously.
Scala’s type system and type inference go a long way towards providing the benefits of static typing with the cleaner syntax of dynamic typing, but Scala doesn’t eliminate the extra complexity of static typing.
Recall my Observer example from the first blog post, where I used traits to implement it.
trait Observer[S] {
def receiveUpdate(subject: S);
}
trait Subject[S] {
this: S =>
private var observers: List[Observer[S]] = Nil
def addObserver(observer: Observer[S]) = observers = observer :: observers
def notifyObservers() = observers.foreach(_.receiveUpdate(this))
}
In Ruby, we might implement it this way.
module Subject
def add_observer(observer)
@observers ||= []
@observers << observer # append, rather than replace with new array
end
def notify_observers
@observers.each {|o| o.receive_update(self)} if @observers
end
end
There is no need for an Observer
module. As long as every observer responds to the receive_update
“message”, we’re fine.
I commented the line where I append to the existing @observers
array, rather than build a new one, which would be the FP and Scala way. Appending to the existing array would be more typical of Ruby code, but this implementation is not as thread safe as an FP-style approach.
The trailing if
expression in notify_observers
means that nothing is done if @observers
is still nil
, i.e., it was never initialized in add_observer
.
So, which is better? The amount of code is not that different, but it took me significantly longer to write the Scala version. In part, this was due to my novice chops, but the reason it took me so long was because I had to solve a design issue resulting from the static typing. I had to learn about the typed self construct used in the first line of the Subject
trait. This was the only way to allow the Observer.receiveUpdate
method accept to an argument of type S
, rather than of type Subject[S]
. It was worth it to me to achieve the “cleaner” API.
Okay, perhaps I’ll know this next time and spend about the same amount of time implementing a Ruby vs. Scala version of something. However, I think it’s notable that sometimes static typing can get in the way of your intentions and goal of achieving clarity. (At other times, the types add useful documentation.) I know this isn’t the only valid argument you can make, one way or the other, but it’s one reason that dynamic languages are so appealing.
Poly-paradigm Languages vs. Mixing Several Languages
So, you’re convinced that you should use FP sometimes and OOP sometimes. Should you pick a poly-paradigm language, like Scala? Or, should you combine several languages, each of which implements one paradigm?
A potential downside of Scala is that supporting different modularity paradigms, like OOP and FP, increases the complexity in the language. I think Odersky and company have done a superb job combining FP and OOP in Scala, but if you compare Scala FP code to Haskell or Erlang FP code, the latter tend to be more succinct and often easier to understand (once you learn the syntax).
Indeed, Scala will not be easy for developers to master. It will be a powerful tool for professionals. As a consultant, I work with developers with a range of skills. I would not expect some of them to prosper with Scala. Should that rule out the language? NO. Rather it would be better to “liberate” the better developers with a more powerful tool.
So, if your application needs OOP and FP concepts interspersed, consider Scala. If your application needs discrete services, some of which are predominantly OOP and others of which are predominantly FP, then consider Scala or Java for the OOP parts and Erlang or another FP language for the FP parts.
Also, Erlang’s Actor model is more mature than Scala’s, so Erlang might have an edge for a highly-concurrent server application.
Of course, you should do your own analysis…
Final Thoughts
Java the language has had a great ride. It was a godsend to us beleaguered C++ programmers in the mid ‘90’s. However, compared to Scala, Java now feels obsolete. The JVM is another story. It is arguably the best VM available.
I hope Scala replaces Java as the main JVM language for projects that prefer statically-typed languages. Fans of dynamically-typed languages might prefer JRuby, Groovy, or Jython. It’s hard to argue with all the OOP and FP goodness that Scala provides. You will learn a lot about good language and application design by learning Scala. It will certainly be a prominent tool in my toolkit from now on.
The Ascendency of Dynamic X vs. Static X, where X = ... 23
I noticed a curious symmetry the other day. For several values of X, a dynamic approach has been gaining traction over a static approach, in some cases for several years.
X = Languages
The Ascendency of Dynamic Languages vs. Static Languages
This one is pretty obvious. It’s hard not to notice the resurgent interest in dynamically-typed languages, like Ruby, Python, Erlang, and even stalwarts like Lisp and Smalltalk.
There is a healthy debate about the relative merits of dynamic vs. static typing, but the “hotness” factor is undeniable.
X = Correctness Analysis
The Ascendency of Dynamic Correctness Analysis vs. Static Correctness Analysis
Analysis of code to prove correctness has been a research topic for years and the tools have become pretty good. If you’re in the Java world, tools like PMD and FindBugs find a lot of real and potential issues.
One thing none of these tools have ever been able to do is to analyze conformance of your code to your project’s requirements. I suppose you could probably build such tools using the same analysis techniques, but the cost would be too prohibitive for individual projects.
However, while analyzing the code statically is very hard, watching what the code actually does at runtime is more tractable and cost-effective, using automated tests.
Test-driving code results in a suite of unit, feature, and acceptance tests that do a good enough job, for most applications, of finding logic and requirements bugs. The way test-first development improves the design helps ensure correctness in the first place.
It’s worth emphasizing that automated tests exercise the code using representative data sets and scenarios, so they don’t constitute a proof of correctness. However, they are good enough for most applications.
X = Optimization
The Ascendency of Dynamic Optimization vs. Static Optimization
Perhaps the least well known of these X’s is optimization. Mature compilers like gcc have sophisticated optimizations based on static analysis of code (you can see where this is going…).
On the other hand, the javac compiler does not do a lot of optimizations. Rather, the JVM does.
The JVM watches the code execute and it performs optimizations the compiler could never do, like speculatively inlining polymorphic method calls, based on which types are actually having their methods invoked. The JVM puts in low-overhead guards to confirm that its assumptions are valid for each invocation. If not, the JVM de-optimizes the code.
The JVM can do this optimization because it sees how the code is really used at runtime, while the compiler has no idea when it looks at the code.
Just as for correctness analysis, static optimizations can only go so far. Dynamic optimizations simply bypass a lot of the difficulty and often yield better results.
Steve Yegge provided a nice overview recently of JVM optimizations, as part of a larger discussion on dynamic languages.
There are other dynamic vs. static things I could cite (think networking), but I’ll leave it at these three, for now.
Strongly Typed Languages Considered Dangerous 75
Have you ever heard of covariance? Method parameters or returns are said to be covariant if, as you work your way down an inheritance hierarchy, the types of the formal parameters or return type in an overridden method can be sub-types of the formal parameters and return types in the superclass’ version of the method.
Oh, and contravariance is just the opposite.
What?! Why should you care? Answer, you shouldn’t. Yes C++ and Java both support covariant return types, but so what? Have you ever used them? OK, I have, but then I also used and liked C++ for about 7 years, over 10 years ago. We all learn to move on.
You ever notice how something meant to help often (typically?) turns out to do exactly the opposite? Even worse, it directly supports or enables another unfortunate behavior. This is just Weinberg’s first rule of problem solving:
Every solution introduces new problems
Here’s an example I’m guilty of. Several years ago I was working on a project where we had written many unit tests. We had not followed the FIRST principles, specifically R-Reliability. Our tests were hugely affected by the environment. We had copies of real databases that would get overridden without warning. We’d have problems with MQ timeouts at certain times of the day or sometimes for days. And our tests would fail.
We wold generally have “events” that would cause tests to fail for some time. We were using continuous integration and so to “fix” the problem, I created a class we called “TimeBomb” – turns out it was the right name for the wrong reason.
You’d put a TimeBomb in a test and then comment out the test. The TimeBomb would be given a date. Until that date, the test would “pass” – or rater be ignored. At some point in the future, the TimeBomb would expire and tests would start failing again.
I was so proud of it, I even took the time to write something up about it here. I had nothing but the best intentions. I wanted an active mechanism to remind us of work that needed to be done. We had so many todo’s and warnings that anything short of an active reminder would simply be missed. I also wanted CI to “work.” But what eventually happened was that as our tests kept failing, we’d simply keep moving the TimeBomb date out.
I wrote something that enabled our project to collect more design debt. As I said, my intentions were noble. But the road to Hell is paved with good intentions. Luckily I got the name right. The thing that was really blowing up, however, was the project itself.
What has all of this got to do with “strongly typed languages”?
If you’re working with a strongly typed language, you will have discussions about covariance (well I do anyway). You’ll discuss the merits of multiple inheritance (it really is a necessary feature – let the flames rise, I’m already in Hell from my good intentions). You’ll also discuss templates/generics. The list goes on and on.
If you’re working with a dynamically typed language (the first one I used professionally was Smalltalk but I also used Self, Objective-C, which lets you choose, and several other languages whose names I do not recall).
Covariance is not even an issue. The same can be said of generics/templates and yes, even multiple inheritance is less of an issue. In a strongly-typed languages, things like Multiple Inheritance are necessary if you want your type system to be complete. (If you don’t believe me, read a copy of Object Oriented Software Construction, 2nd ed. by Bertrand Meyer – an excellent read.)
Ever created a mock object in a typed language? You either need an interface or at least a non-final class. What about Ruby or Smalltalk? Nope. Neither language cares about a class’ interface until it executes. And neither language cares how a class is able respond to a particular message, just that it does. It’s even possible in both languages to NOT have a method and still work if you mess with the meta-language protocol.
OK, but still, does this make typed languages bad?
Lets go back to that issue of enabling bad things.
Virtual Machines, like the JVM and CLR, have made amazing strides in the past few years. Reflection keeps getting faster. Just in time compilers keep getting smarter. The time required for intrinsic locks has improved and now a Java 5 compiler will support non-blocking, safe, multi-threaded updates. Modern processors support such operations using an optimistic approach. Heck, Java 6 even does some cool stuff with local object references to significantly improve Java’s memory usage. Java runs pretty fast.
Dynamic languages, generally, are not there yet. I hope they get there, but they simply are not there. But so what?! If your system runs “fast enough” then it’s fast enough. People used Smalltalk for real applications years ago – and still do to some extent. Ruby is certainly fast enough for a large class of problems.
How is that? These languages force you to write well. If you do not, then you will write code that is not “fast enough.” I’ve see very poor performing Smalltalk solutions. But it was never because of Smalltalk, it was because of poor programming practices. Are there things that won’t currently perform fast enough in dynamically typed languages? Yes. Are most applications like that? Probably not.
You can’t get away with as much in a dynamically typed language. That sounds ironic. On the one hand you have amazing power with dynamically typed languages. Of course Peter Parker learned that “With great power comes great responsibility.” This is just as true with Ruby and other dynamically typed languages.
You do not have as much to guide you in a dynamically typed language. Badly written, poorly organized code in a typed language is hard to follow but it’s possible. In a language like Ruby or Smalltalk it’s still possible but it’s a lot harder. Such poor approaches will typically fail sooner. And thats GOOD! You’ve wasted less money because you failed sooner. If you’ve got crappy design, you’re going to fail. The issue is how much time/money will you spend to get there.
Another thing that strongly typed languages offer is a false sense of security because of compiler errors. I have heard many people deride the need for unit tests because the compiler “will catch it.”
This misses a significant point that unit tests can serve as a specification of behavior rather than just a validation of as-written code.
You cannot get away with as much in a dynamically typed language. Or put another way, a dynamically typed language does not enable you to be as sloppy. It’s just a fact. In fact you can typically get away with a lot in a dynamically typed language, you just have to do it well.
Does this mean that dynamically typed languages are harder to work in? Maybe. But if you follow TDD, then the language is less important.
Do we need fast, typed languages? Clearly we do. There are applications where having such languages is necessary. Device drivers are not written in Ruby (maybe they are, I’m just trying to be more balanced).
However, how many of you were around when games were written in assembly? C was not fast enough. Then C was fast enough but C++ was still a question mark. C++ and C became mainstream but Java was just too slow. Some games are getting written in Java now. Not many, but it’s certainly possible. It is also possible to use multiple languages and put the time-critical stuff, which is generally a small part of your system, in one language and use another language for the rest of the system.
Back in the very early 90’s I worked just a little bit with Self. At that time, their Just In Time compiler would create machine code from byte code for methods that got executed. They went one step further, however. Not only did they JIT compile a method for an object, they would actually do it for combinations of receivers and type parameters.
There’s a name for this. In general it is called multi-dispatch (some languages support double-dispatch, the visitor pattern is a mechanism to turn standard polymorphism into a weak form of double dispatch, Smalltalk used a similar approach for handling numerical calculations).
Self was doing this not to support multi-dispatch but to improve performance. That means that a given method could have multiple compiled forms to handle commonly used methods on a receiver with commonly-used parameters. Yes it used more memory. But given what modern compilers do with code optimization, it just seems that this kind of technique could have huge benefits in the performance of dynamically typed languages. This is just one way a dynamic language can speed things up. There are others and they are happening NOW.
I’m hoping that in the next few years dynamic languages will get more credit for what they have to offer. I believe they are the future. I still primarily develop in Java but that’s just because I’m waiting for the dust to settle a little bit and for a clear dynamic language to start to assert itself. I like Ruby (though its support for block closures is, IMO, weak). I’m not convinced Ruby is the next big thing. I’m working with it a little bit just in case, however.
What are you currently doing that enables yourself or your co-workers to maintain the status quo?