Skip to content

Improving the STM: Multi-Version Concurrency Control

10
Nov
2008

I wrote a post some time ago introducing a software transactional memory (STM) framework for Scala based on implicit conversions.  This framework had some nice features, like a very clean syntax coupled with compile-time verification of transactional semantics, but it really wasn’t ready for real-world use.  There was one very serious skeleton in the closet that I carefully avoided as I went through the presentation: non-deterministic behavior was still possible, even within a transaction.

The Problem

Allow me to illustrate.  Imagine two transactions, each executing concurrently in a world with two reference cells.  One transaction reads from both cells, subtracts one from the other and then uses the result as a divisor.  The other transaction increments both cells in lock-step with each other.  Put into code, this would look something like the following:

val a = new Ref(1)
val b = new Ref(0)
 
def compute(x: Int)(implicit t: Transaction) = x / (a - b)
 
def interfere(implicit t: Transaction) {
  a := a + 1
  b := b + 1
}
 
atomic(compute(3)(_))         // in thread-1
...
atomic(interfere(_))          // in thread-2

Incidentally, I know that the whole interfere(_) syntax is a bit ugly.  Voice your opinion on RFE #1492.

This series of computations, while useless, should work just fine under normal circumstances.  The STM is going to verify that neither transaction is stepping on the other with any lasting effect, but unfortunately there are things that can go wrong before we get to “lasting”.  It may not be obvious, but this code actually has a very real probability of throwing an ArithmeticException due to a division by zero.

This unfortunate circumstance arises when the “interfere” transaction commits after the “compute” transaction has read a value for a but before it reads a value for b.  When this occurs, compute will obtain a value for b which is precisely the same as the value it has already read from a.  Now, interfere has also changed the global state of a to preserve its property of leading b by 1, but there is no way for compute to know that: it has already extracted a value for a, it’s not going to check to ensure that it’s still sacred.  What happens next is of course trivial: with a and b having the same value, the subtraction operation results in 0, causing the division to fail.

Note that this issue does not indicate a failure in the safeguards, just an incomplete specification for the STM.  If the reference fault had not resulted in an exception, the commit process for the “compute” transaction would have caught the problem and retried the transaction.  In other words, if the problem hadn’t caused a catastrophic failure within the transaction, the STM would have gracefully recovered and no one would have been any the wiser.

When I created the STM implementation, I recognized that this case can indeed arise.  To compensate for this, I created a dumb catch-all within the transaction dispatch process.  This code literally swallows all exceptions coming from within the transaction, treating them as signals to abort and retry.  This works-around our exceptional-interferance issue from above, but it does bring consequences of its own:

def badBoy(implicit t: Transaction) {
  throw RuntimeException("Er...")
}

This transaction will always throw an exception, regardless of the state of the references or any other transactions.  With the originally envisioned design, this exception would have simply propagated out from where the transaction was kicked off (wherever the atomic method was called) and life would have been happy.  Unfortunately, with our catch-all in place (required to solve the earlier problem) this code will fall into an infinite loop.

The problem is that the catch-all will field the RuntimeException and assume that it’s a divine signal that trouble is on the way.  This assumption that data-integrity has been somehow compromised leads the STM to simply retry the transaction.  However, since the exception is thrown every time, the catch-all will just keep on swallowing the same exception, endlessly-retrying the same tired transaction.  Naturally, this is just a trivial example, but it’s not difficult to imagine a block of code which really does throw a legitimate exception, one which we would want to propagate back into user-land where we can effectively deal with the situation.  An infinite loop of failing transactions is not the right way to handle the problem.

Speaking of infinite loops, we still haven’t really solved our interference problem from above.  In our first snippet, we created a data contention situation which could non-deterministically cause an exception to be thrown.  In order to solve this problem, we introduced a catch-all to swallow the exception and just keep right on trucking.  This “solution” not only introduces some undesired behavior (swallowing legitimate exceptions), but it also fails to really fix the issue.  Consider the following slight variation on our previous theme:

val a = new Ref(1)
val b = new Ref(0)
 
def maybeLoop(implicit t: Transaction) {
  while (a <= b) {
    // should never happen
  }
}
 
def interfere(implicit t: Transaction) {
  a := a + 1
  b := b + 1
}

In case it isn’t obvious, the while-loop in maybeLoop should never actually execute.  We have defined references a and b to have values such that a is always strictly greater than b.  The only transaction which modifies these values preserves this property, and so by easy induction we can prove that the conditional “a <= b” is in fact a contradiction.

Unfortunately, our little hand-wavy proof doesn’t take non-determinism into account.  Remember from the first example where the “interfere” transaction committed between the reads for a and b?  Earlier, this just raised an ArithmeticException.  However, in this case, such a conflict would actually cause maybeLoop to suddenly become unterminating!  Because of the way that this data contention falls out, we could end up with an infinite loop where we had expected no loop at all.

This is very, very bad for a number of reasons.  With our exception problem, we were just able to introduce a catch-all to get around the issue.  The same trick isn’t going to work with an infinite loop.  Control is never returned to the transaction controller, so there is no way to inject any logic in this case.  What’s (possibly) worse is there is no way for us to even try to detect these sorts of situations.  This is actually a slight specialization of the halting problem.  Briefly: because any Turing Complete language must be capable of non-terminating execution, the introduction of non-deterministic evaluation into such a language can result in non-deterministic non-terminating execution.  Since it is impossible in general for one Turing Machine to determine whether or not another will halt on a given input, it must follow that it is also impossible to determine whether or not a non-deterministic Turing Machine will halt.  My logical form is atrocious, but I think you get the picture.

The Solution

The point is that we have a problem, and cop-out solutions like a catch-all exception handler isn’t going to solve it.  The only real answer1 would be to somehow ensure that when “interfere” commits, it doesn’t actually change the values of a and b as visible to the other running transaction.  In other words, transactional commits should change the global state, but leave any transaction-local state untouched.  That way, in-progress transactions can work with the reference values which were in place at the start of the transaction, deterministically work their way down to the bottom and finally either commit or detect conflicts and retry.  With this solution, exceptions can be propagated out to user-land because every exception will be valid, none will ever be caused by a reference fault.

In a nutshell, this strategy is like giving each transaction its own “snapshot of the world”.  From the moment the transaction starts, it remains entirely oblivious to any changes going on in the world outside.  It’s as if each transaction got to stop the universe, do its work, commit and then allow things to pick up right where they left off.  However, instead of actually stopping the universe, each transaction will preserve all reference state from that exact moment and allow everyone else to keep right on rolling.  In technical terms, this is called MVCC: Multi-Version Concurrency Control.

If we apply this technique to our first dilemma involving compute and interfere, we will find that even when interfere commits between the reads of a and b, there really is no issue.  Rather than getting a new value of b which is inconsistent with its already-retrieved old value of a, the “compute” transaction will just see the old values for both a and b.  None of the changes to the reference world outside of compute are observed until after the transaction completes.  At that point, the STM will detect any reference faults (such as interfere causing interference) and if necessary, retry the transaction.

The Implementation

This all sounds quite well-and-good, but how would we go about creating such a magical fairy land in the sky?  The most intuitive approach would be to copy every live reference whenever we start a transaction.  This isn’t really as bad as it sounds, seeing as any data contained within a reference must be immutable, so copies are literally free (just use the original data).  Unfortunately, that doesn’t stop the entire process from being heinously inefficient.  It’s easy to imagine a system with 100,000 references and a tiny little transaction which only uses 2 of them.  Do we really want to loop through 100,000 references just to preserve a pair of them?

Obviously, a different approach is needed.  That’s where the committal process comes into play.  With the old design, when a transaction commits, it would first check for any reference faults, then if everything was dandy it would write the necessary changes to the global state for each reference.  In code it looked like this:

def commit() = {
  if (world.size > 0) {
    CommitLock.synchronized {
      val back = world.foldLeft(true) { (success, tuple) =>
        val (ref, _) = tuple
        success && ref.rev == version(ref)
      }
 
      if (back) {
        for (ref <- writes) {
          ref.contents = (world(ref), rev)
        }
      }
 
      back
    }
  } else true
}

MVCC is going to require this commit method to do a little extra legwork.  Rather than just writing the changes into the global state, commit will need to loop through any active transaction, saving the old values for only the modified references within each of these transaction’s logs.  This dramatically reduces the amount of looping and saving which takes place without actually imposing too much extra overhead.  This change - combined with a separation of reads and writes within a transaction - actually looks like the following:

def preserve[T](ref: Ref[T]) {
  val castRef = ref.asInstanceOf[Ref[Any]]
 
  if (!world.contains(castRef)) {
    val (v, rev) = ref.contents
 
    world(castRef) = v
    version(castRef) = rev
  }
}
 
def commit() = {
  if (world.size > 0) {
    CommitLock.synchronized {
      val f = { ref: Ref[Any] => ref.rev == version(ref) }
      val back = reads.forall(f) && writes.forall(f)
 
      if (back) {
        for (ref <- writes) {
          for (t <- Transaction.active) {
            if (t != this) t.preserve(ref)       // preserve the old contents of t
          }
 
          ref.contents = (world(ref), rev)
        }
      }
 
      back
    }
 
  } else true
}

Amazingly enough, this tiny little change is all that is required to implement MVCC within our STM.  Well, of course I’m skipping the implementation of Transaction.active as well as the bitsy concurrency semantics required to make it all work, but you weren’t really interested in any of that, were you?

For those who are keeping score, Clojure already has MVCC implemented within its hallmark STM.  As a point of interest, the implementation is subtly different from the one I have just outlined.  Instead of placing the old reference values within the local transaction logs, Clojure stores these contents as old versions of the TVar within the reference itself.  I’m not sure if I like this approach better or not, but it certainly seems to work well!  In either case, the semantics are essentially identical.

Final aside worthy of mention: you need not concern yourself that all of this extra copying is going to eat through heap space.  This is one of the huge benefits of immutable data: copies are free.  We aren’t really copying the data, just maintaining a reference to its previous version.  Once the transaction commits, its log will go out of scope and that old reference will be eligible for garbage collection.  All in all, the addition of MVCC to our STM doesn’t raise the overhead even the slightest bit.

Conclusion

Realizing that my blog was not the most convenient way to distribute project files for a semi-serious library like the Scala STM, I decided to put the project on GitHub.  For those who hate following links, you can checkout, build and test (see below) the library using the following commands (assuming you have both Git and Buildr installed):

  git clone git://github.com/djspiewak/scala-stm.git
  cd scala-stm
  buildr

In a flurry of activity besides the addition of MVCC, I have also added some reasonably-comprehensive BDD specs to ensure that the correctness of the implementation isn’t just in my head.  Of course, all of the tests are probabilistic, but I think the library is finally to a point where they can be relied upon.  If you like, I’ll even distribute a proper pre-built JAR.

STM is hardly in its infancy, but it is just now starting to catch on as a viable alternative to the classic locking techniques favored by concurrent programs everywhere.  Improvements like MVCC promise even better reliability and throughput, further advancing the goal of deterministic asynchronous programs which are easy to develop and even easier to reason correct.

1 There actually is another solution involving conflict detection on the fly, but it’s a little inefficient and not really as subjectively elegant as MVCC.

Scala Collections for the Easily Bored Part 1: A Tale of Two Flavors

21
Jul
2008

One of the most obvious things to a Java developer first coming into Scala-land is the radically different Collections API included as part of the standard library.  For the most part, we use the same frameworks and APIs in Scala as are available in Java.  This is natural, thanks to the extremely tight integration between the two languages.  So why is this one area such a startling departure from Scala’s heritage?

The answer has to do with what the Scala language is syntactically capable of handling.  Scala isn’t just an object-oriented language, it is also highly functional.  It is only natural that such an integral part of the core libraries would reflect this fact.  Unfortunately, most developers fail to take full advantage of the power offered by the collections API.  Despite the available power, most code written using Scala’s collections tends to look a lot like Java in disguise.

I had actually planned on addressing this topic in a single article.  However, Scala’s collections are so vast and powerful (sounds like one of Roddenberry’s alien consciousness beings) that it really would overrun the limits of conventional blogging to attempt to cover it all in a single post.  Despite the fact that I’ve been creating mammoth anthologies of late, I think it’s probably better to break it into bite-sized chunks.  First up: the confusing dual nature of Scala’s collections API!

A Tale of Two Flavors

The very first thing developers notice when looking at Scala’s collections is a (seemingly) odd redundancy in the specification.  Looking under the scala.collection package, we see not one, but three separate sub-packages, each containing what seem to be reimplementations of the same types.  For example, consider the following three traits:

I don’t know about you, but this confused the heck out of me the first time I really dug into Scala’s standard API.  Actually, it gets even worse when you discover that there is also a trait (and companion object), scala.collection.Map, which is actually a super-trait of the three listed above.  Seems like Dr. Odersky discovered the magic of separated namespaces and reacted like a two-year-old on espresso.

As it turns out, there’s a very logical reason for having these separated and seemingly-redundant collections packages.  Of the three, one of them can be discounted immediately as uninteresting.  The jcl package contains collections, but they are merely wrappers for the corresponding Java collections.  This allows more efficient transmutation between the Java collections API and Scala.  It is almost never necessary to use this package directly, since a number of implicit conversions are built into Scala to make the process essentially transparent.

Of course, this still leaves the immutable and mutable packages.  This distinction traces back to some of Scala’s functional roots.  As you are likely aware, Scala supports both mutable and immutable variables, as denoted by the var and val keywords, respectively.  While there are some significant differences at compile-time, conceptually, the only distinction between these two types is the former allows reassignment whereas the latter does not.  Mutable variables are a common - and indeed, essential - feature in imperative languages (Java, C++, Ruby, etc).  For example, here’s how we would sum an array of integers in Java:

int[] numbers = ...
 
int sum = 0;
for (int n : numbers) {
    sum += n;   // reassign sum to accumulate n
}

In this case, sum is a mutable variable which accumulates the total value of all numbers in the array, numbers.  Theoretical disputes aside, this style of programming is simply impossible in certain languages.  For example, SML provides no mechanism for declaring mutable variables.  So if we want to sum the values in a list of ints, we have to do it in some other way (code provided for the curious):

fun sumList ls = foldl (op +) 0 ls

In Scala, both of these techniques are available to us.  However, despite providing for mutable state, Scala does encourage developers to avoid it.  The reasoning behind this is that mutable state has a tendency to make code more difficult to reason about, making testing much harder.  Also, as any experienced developer will tell you, mutable state kills concurrency.

Not only does Scala encourage the use of immutable variables, it also encourages the use of immutable collections.  This concept may seem a little bizarre to those of you coming from an imperative background (I know it did to me), but it actually works.  While a map container which cannot be modified probably seems a little useless, it actually provides a startling degree of freedom from concerns like concurrent locking and unintended side-effects.  With immutable collections, you are able to manipulate objects with perfect assuredness that no method call will “accidentally” alter its contents.  How many times have you cloned a collection prior to returning it?  Or how often have you dug through someone else’s code just to assure yourself that it is safe to call a given method passing an instance of List?  Immutable data structures completely solve that problem.

Naturally, immutable collections require very different patterns and idioms than those which are mutable.  My ML code from above illustrates this to a very small degree, using a fold to traverse an immutable list.  As a general rule, immutable data structures are only useful when being passed around via method calls.  A common pattern is to build up a data structure recursively, creating a new instance with one more element at each invocation:

def toSet[T](list: List[T]) = {
  def traverse(list: List[T])(set: Set[T]): Set[T] = list match {
    case hd :: tail => traverse(tail)(set + hd)   // create a new Set, adding hd
    case Nil => set
  }
 
  traverse(list)(Set[T]())
}
 
val names = List("Daniel", "Chris", "Joseph", "Renee")
val nameSet = toSet(names)

At each recursive invocation of traverse, a new Set is created, based on the contents of the old one, set, but with one additional member, hd.  At the same time, we are deconstructing an immutable List, list, selecting its first element and whatever remains at each step.  Whenever you work with immutable data structures, you will see a lot of code which looks like this.

Of course, the natural question which comes to mind is, what about performance?  If each invocation actually creates a brand new Set for every recursive call, doesn’t that require a lot of inefficient object copying and heap operations?  Well, as it turns out, this isn’t really the case.  Yes, a new instance must be created at each turn, which is is a comparatively expensive operation on the JVM, but almost nothing is being copied.  All of Scala’s immutable data structures have a property known as persistence, which means that you don’t copy the data out of the old container when creating a new, you just have the new reference the old and treat all of its contents as if they were its own.  A linked list is a good example of this, since each node of a list contains exactly one element, as well as a reference to another node.  If we think of each node as a representative of a list starting with itself and traversing to the end, then list suddenly becomes a fully persistent data structure (since the new list contains a sub-list in its entirety and additive operations require no data copying).  Rich Hickey, the creator of Clojure (a Lisp dialect running on the JVM), has an excellent presentation which explains some of the hows and whys behind this technique (as well as some other interesting topics).  Chapter 19 of Programming in Scala (Odersky, Spoon & Venners) also has a good example of a persistent immutable queue.

Conclusion

I happen to like immutable data, so most of this series uses the scala.collection.immutable package.  However, there are certainly situations where mutable data structures are the only way to go, either because of system requirements or for performance reasons.  Fortunately, Scala’s mutable collections have an almost identical interface to its immutable collections.  Thus, most of the in formation presented here is applicable to both branches of the library.

Now that we have laid a basic foundation regarding the fundamentals of Scala’s collections framework, we can move onto more interesting things.  The next installment will deal with fold and map, the bread and butter of every collection.

The Brilliance of BDD

9
Jun
2008

As I have previously written, I have recently been spending some time experimenting with various aspects of Scala, including some of the frameworks which have become available.  One of the frameworks I have had the privilege of using is the somewhat unassumingly-titled Specs, and implementation of the behavior-driven development methodology in Scala.

Specs takes full advantage of Scala’s flexible syntax, offering a very natural format for structuring tests.  For example, we could write a simple specification for a hypothetical add method in the following way:

object AddSpec extends Specification {
  "add method" should {
    "handle simple positives" in {
      add(1, 2) mustEqual 3
    }
 
    "handle simple negatives" in {
      add(-1, -2) mustEqual -3
    }
 
    "handle mixed signs" in {
      add(1, -2) mustEqual -1
      add(-1, 2) mustEqual 1
    }
  }
}

We could go on, of course, but you get the picture.  This code will lead to the execution of four separate assertions in three tests (to put things into JUnit terminology).  Fundamentally, this isn’t too much different than a standard series of unit tests, just with a slightly nicer syntax.

Specs defines a domain-specific language for structuring test assertions in a simple and intuitive way.  However, this is hardly the only framework for BDD.  Perhaps the most well-known such framework is RSpec, which answers a similar use-case in the Ruby programming language.  Our previous specification could be rewritten using RSpec as follows:

describe AddLib do
  it 'should handle simple positives' do
    add(1, 2).should == 3
  end
 
  it 'should handle simple negatives' do
    add(-1, -2).should == -3
  end
 
  it 'should handle mixed signs' do
    add(1, -2).should == -1
    add(-1, 2).should == 1
  end
end

The end-result is basically the same: the add method will be tested against the given assertions (all four of them) and the results printed in some sort of report form.  In this area, RSpec is significantly more mature than Specs, generating very slick HTML reports and nicely formatted console output.  This isn’t really a fundamental weakness of the Specs framework however, just indicative of the fact that RSpec has been around for a lot longer.

These two frameworks are interesting of course, but they are merely implementations of a much larger concept: behavior-driven development.  I’ve never been much of a fan of unit testing.  It’s always seemed to be incredibly dull and a very nearly fruitless waste of effort.  As much as I hate it though, I have to bow to the benefits of a self-contained test suite; and so I press on, cursing JUnit every step of the way.

BDD provides a nice alternative to unit testing.  At its core, it is not much different in that test groupings and primitive assertions are used to check all aspects of a test unit against predefined data.  However, there is something about the “flow” of a behavioral spec that is considerably easier to deal with.  For some reason, it is far less painful to devise a comprehensive test suite using BDD principles than conventional unit testing.  It seems a little far-fetched, but BDD actually makes it easier to write (and more importantly, formulate) exactly the same tests.

It’s an odd phenomenon, one which can only be caused by the storyboard flow of the code itself.  It is very natural to think of distinct requirements for a test unit when each of these requirements are being labeled and entered in a logical sequence.  Moreover, the syntax of both Specs and RSpec is such that there is very little boiler-plate required to setup an additional test.  Compare the previous BDD specs with the following JUnit4 example:

public class MathTest {
    @Test
    public void testSimplePositives() {
        assertEquals(3, add(1, 2));
    }
 
    @Test
    public void testSimpleNegatives() {
        assertEquals(-3, add(-1, -2);
    }
 
    @Test
    public void testMixedSigns() {
        assertEquals(-1, add(1, -2));
        assertEquals(1, add(-1, 2));
    }
}

JUnit just requires that much more syntax.  It breaks up the logical flow of the tests and (more importantly) the developer train of thought.  What can be worse is this syntax bloat makes it very tempting to just group all of the assertions into a single test - to save typing if nothing else.  This is problematic because one assertion may shadow all the others in the case of a failure, preventing them from ever being executed.  This can make certain problems much more difficult to isolate.

Logical flow is extremely important to test structure.  BDD frameworks provide a very nice syntax for painlessly defining comprehensive test suites.  The really wonderful thing about all of this is that BDD is available on the JVM, right now.  There’s nothing stopping you from writing your code in Java as you normally would, then creating your test suite in Scala using Specs rather than JUnit.  Alternatively, you could use RSpec on top of JRuby, or Gspec with Groovy.  All of these are seamless replacements for a test framework like JUnit, and requiring of far less syntactic overhead.

The growing move toward polyglot programming encourages the use of a separate language when it is best suited to a particular task.  In this case, several languages are available which offer far more powerful test frameworks than those which can be found in Java.  Why not take advantage of them?

The Problem of Perspective Multiplicity

2
Jun
2008

Some six years ago, I switched my primary IDE from NetBeans to Eclipse JDT (then 2.0).  At the time, I did this primarily because NetBeans was too much of a resource hog for my pathetic development machine, but I quickly learned to appreciate the power of the Eclipse development environment.  NetBeans has since made great strides of course, but at the time, Eclipse was lightyears beyond it in both features and polish.

One of the more interesting features offered by Eclipse was the concept of a “perspective”, a collection of views in a specific layout conducive to performing a specific series of tasks.  The major upshot of this was instead of the debugger views popping in and out, they simply remained hidden in a separate perspective, ready to restore to your customized configuration as necessary.  This innovation was also present in other areas, such as the CVS Team view and the Update Manager (yes, the Eclipse update system was once a set of views and editors).

You could switch between these perspectives manually of course, but most of the time Eclipse was able to just detect which perspective you needed and make the switch automatically.  If you were to launch an application in debug mode for example, the “Debug” perspective would be opened automatically, bringing useful views to the fore.  Once you were done debugging, it was easy to switch back to the “Java” perspective for more streamlined editing.  It was a good system, and it worked well.

Unfortunately, times have changed.  Don’t get me wrong, I still love having all my debug views and layout saved for me in a discrete section of the app, ready to access on a moment’s notice.  But Eclipse is no longer the single-purpose application it once was.  Yes, I know that it has always been billed as “an open tool platform for everything and nothing in particular”, but back in the day (and especially before OSGi) most people had yet to realize this.  The only language supported by Eclipse on any serious level was Java, thus the perspective system worked extremely well for organizing IDE views.  Now, Eclipse serves as the foundation for IDE frameworks supporting dozens of different languages, requiring an equal (if not greater) number of perspectives.

 image

Even in this screenshot, I’m still hiding easily 70% of the perspectives available to Eclipse.  With all of these different view collections and configurations, it’s no wonder that people often find Eclipse to be confusing compared to other IDEs.  In NetBeans (for example) you can work with as many languages as you want within a single perspective/layout/configuration.  The outline shows the relevant information for whatever file you have open, and the project explorer view is fully integrated with each language, showing all available projects and their associated structure.  Most importantly, this view is able to show project logical structure as dictated by the support module for that language (e.g. src/, test/, etc).

Effectively, other IDEs have evolved a single “Development” perspective, one which shows a generic set of views common to all languages.  Unlike Eclipse, which requires switching to the Ruby perspective or the C/C++ perspective to get the appropriate project viewer, NetBeans has one project viewer which is extensible by any module.  Eclipse has some of this with the Package Explorer, but some plugins like DLTK don’t properly integrate and so the view isn’t as streamlined.  Additionally, some functions like “Open Type” don’t work appropriately unless in the corresponding perspective for a given language.

Yes, I am aware that I could simply open any views I want within a single perspective, but that’s not what I’m looking for.  I don’t want to open five different views for navigating project files, I want to have one master view which shows me everything through the filter of whatever language is relevant to the project.  Project Explorer comes close, but it fails to handle the tighter integration (such as the “Referenced Libraries” in JDT or script outlines for DLTK).

Theoretically, Eclipse only needs four or five perspectives for the average developer working with any number of languages: Develop, Debug, Test, Repositories, Synchronize.  Obviously, more perspectives would be needed for functionality which does not conform to normal development conventions (such as “Planning” or even “Email”), but I think that these core perspectives could provide a consistent, generic framework to which any language IDE could conform.  We can already see something similar happening with the Debug perspective, which is used by Java, Ruby, Scala and C/C++ alike.

What is needed is a common super-framework to be extended by actual language implementations such as JDT, CDT and the like (similar to what DLTK provides but more encompassing).  This framework should provide a common platform with features such as project viewing, outline, documentation, type hierarchy, call hierarchy, open type, etc.  This platform would then be specialized by the relevant IDE and the same views would allow extension to fit the needs of the language in question.  This already happens with the Outline view, but it needs to occur with other common functions as enumerated.  Views which are not common to different languages (such as Ant Build or Make Targets) would of course not be contained within this super-framework, but would be separate views as they are now.  This framework would allow a developer to use a single set of views for any language, never requiring a workflow-disrupting change of perspective.

The building blocks are all in place, and such an effort would still be in line with the Eclipse philosophy of total extensibility, it’s merely a question of implementation and opinion.  The implementation is simple, as I said, most of the functionality is already available (often redundantly) in any one of the many IDE packages.  The bigger challenge is to convince those who have the power to make the decision.  Eclipse 4.0 is coming, it should be an interesting road to follow.

The Plague of Polyglotism

28
Apr
2008

For those of you who don’t know, polyglotism is not some weird religion but actually a growing trend in the programming industry.  In essence, it is the concept that one should not be confined to a single language for a given system or even a specific application.  With polyglot programming, a single project could use dozens of different languages, each for a different task to which they are uniquely well-suited.

As a basic example, we could write a Wicket component which makes use of Ruby’s RedCloth library for working with Textile.  Because of Scala’s flexible syntax, we can use it to perform the interop between Wicket and Ruby using an internal DSL:

class TextileLabel(id:String, model:IModel) extends WebComponent(id, model) with JRuby {
  require("textile_utils")
 
  override def onComponentTagBody(stream:MarkupStream, openTag:ComponentTag) {
    replaceComponentTagBody(markupStream, openTag, 
        'textilize(model.getObject().toString()))
  }
}
# textile_utils.rb
require 'redcloth'
 
def textilize(text)
  doc = RedCloth.new text
  doc.to_html
end

Warning: Untested code

We’re actually using three languages here, even though we only have source for two of them.  The Wicket library itself is written in Java, our component is written in Scala and we work with the RedCloth library in Ruby.    This is hardly the best example of polyglotism, but it suffices for a simple illustration.  The general idea is that you would apply this concept to a more serious project and perform more significant tasks in each of the various languages.

The Bad News

This is all well and good, but there’s a glaring problem with this philosophy of design: not everyone knows every language.  You may be a language aficionado, picking up everything from Scheme to Objective-C, but it’s only a very small percentage of developers who share that passion.  Many projects are composed of developers without extensive knowledge of diverse languages.  In fact, even with a really good sampling of talent, it’s doubtful you’ll have more than one or two people fluent in more than two languages.  And unfortunately, there’s this pesky concern we all love called “maintainability”.

Let’s pretend that Slava Pestov comes into your project as a consultant and decides that he’s going to apply the polyglot programming philosophy.  He writes a good portion of your application in Java, Lisp and some language called Factor, pockets his consultant’s fee and then moves on.  Now the code he wrote may have been phenomenally well-designed and really perfect for the task, but you’re going to have a very hard time finding a developer who can maintain it.  Let’s say that six months down the road, you decide that your widget really needs a red push button, rather than a green radio selector.  Either you need a developer who knows Factor (hint: there aren’t very many), or you need a developer who’s willing to learn it.  The thing is that most developers with the knowledge and motivation to learn a language have either already done so, or are familiar enough with the base concepts as to be capable of jumping right in.  These developers fall into that limited group of people fluent in many different languages, and as such are a rare find.

Now I’m not picking on Factor in any way, it’s a very interesting language, but it still isn’t very widespread in terms of developer expertise.  That’s really what this all comes down to: developer expertise.  Every time you make a language choice, you limit the pool of developers who are even capable of groking your code.  If I decide to build an application in Java, even assuming that’s the only language I use, I have still eliminated maybe 20% of all developers from ever touching the project.  If I make the decision to use Ruby for some parts of the application, while still using Java for the others, I’ve now factored that 80% down to maybe 35% (developers who know Java and Ruby).  Once I throw in Scala, that cuts it down still further (maybe at 15% now).  If I add a fourth language - for example, Haskell - I’ve now narrowed the field so far, that it’s doubtful I’ll find anyone capable of handling all aspects within a reasonable price range.  It’s the same problem as with framework choice, except that frameworks are much easier to learn than languages.

The polyglot ideal was really devised by a bunch of nerdy folks like me.  I love languages and would like nothing better than to get paid to learn half a dozen new ones (assuming I’m coming into a project with a strange combination I haven’t seen before).  However, as I understand the industry, that’s not a common sentiment.  So a very loud minority of developers (/me waves) has managed to forge a very hot methodology, one which excludes almost all of the hard-working developer community.  If I didn’t know better, I would be tempted to say that it was a self-serving industry ploy to foster exclusivity in the job market.

I want to work on multi-language projects as much as anyone, but I really don’t think it’s the best thing right now.  I’m working on a project now which has an aspect for which Scala would be absolutely perfect, but since I’m the only developer on hand who is remotely familiar with the language, I’m probably going to end up recommending against its adoption.  Consider carefully the ramifications of trying new languages on your own projects, you may not be doing future developers any favors by going down that path.