Wednesday, January 29, 2014

Scala: What’s not to like?

What if you like object-oriented programming and you like functional programming and you like having an interactive interface and you like the Java ecosystem and you like static typing (but you wish the compiler wouldn’t make you be explicit all the time about type declarations)?

Scala lives at the intersection of all those things and, after spending some time with it, I have to say it’s pretty good.

Maybe I’m just weird, but my initial reaction to Scala was that it felt enough like Python to make me happy.  I like Python, but I’ve never tried to use it for anything big—it doesn't feel like I would be comfortable trying to move around in a lot of Python code.  Scala mostly succeeds at feeling simple enough to start diving in scripting-style while having the muscle to build anything you might want a “systems language” for.

Things I Like

A type system that works

I still recall when I first learned that Java's type system made arrays covariant, so that a program could pass the static type checker, not do any explicit dynamic type casting, and still generate run-time type exceptions.  Yay (ironic).  It took years for Java to add generics (parameterized types), and by that time the compatibility concerns meant that it was too late to do things right.  So, Java has static types with generics, but they're not as flexible, powerful, or reliable as you might like.

The type inferencer is good enough that most declarations are for humans, not the compiler.  (There are exceptions, of course.  The most common is that the compiler needs an explicit return type for a recursive function.)

The truth is that the time is long past when I could read a paper about F-bounded polymorphism and imagine that I understood it.  But the Scala type system seems to work in the real world.  Yes, sometimes the error messages are not as helpful as I'd like.  But I'll take that in exchange for what I get in return.

Interactive read-eval-print

Is this really that important?  I think so.  All programming is exploratory to some degree, and all exploration is interactive.  Being able to type in a few lines and see if they produce the result you expect is invaluable.

Value classes

You want zero-overhead wrappers for primitives like integers and doubles?  You want to add behavior to integers by subclassing without any overhead?  You can do that with the combination of value classes and implicits.  This can be surprisingly handy.

Java interoperability

So, Java is not my favorite programming language, but it has an enormous ecosystem.  Scala can plug into that by virtue of its JVM implementation and Java interoperability.  

The interactivity comes into play here as well: Whenever you use someone else’s library, you have to able to do some exploration.  I’ve used both Rhino and Jython as ways to experiment with Java APIs, but they don't feel like an especially good fit, maybe because of the dynamic typing.  Scala works well for this.

Decent IDEs

Well, I've only used IntelliJ IDEA, but people seem to think Eclipse is also fine for Scala.  This isn't so much a good thing about the language as a necessity.

Call by name

This is something that really got done right.  I suppose it’s just syntactic sugar for automatically generating no-argument lambda expressions, but it adds the right kind of control structure extensibility without being, well, weird.

A better Java?

I've seen people say that Scala is to Java as C++ is to C.  Sometimes they mean that as a negative, and sometimes as a positive.  I agree that C++ adds a lot of complexity to C, but I also think it makes a more expressive language that, used with care, makes for efficient, maintainable code.  And that's pretty much how I view Scala.

Things I Don’t Like

There's more than one way to do it

This is, I guess, an unavoidable result of a multi-paradigm language.  Somehow it seems worse in Scala than it does in Python.  Part of that is the syntactic sugar aspect, where there really isn't more than one way to do it, but there's more than one way to express what you're doing.  But another part is that Scala really lives in two worlds: the Java world of mutable data structures and the functional world of immutable data structures and higher-order functions.  You can write code in either style and still be idiomatic, although people are likely to steer you more toward the functional style.

The existence of both traits and abstract classes is another side of this.  Even the language's creator has trouble explaining exactly when to use which.

Strings

The idea of a programming language that doesn’t actually have a native definition of strings is just bizarre (“Scala’s String class is usually derived from the standard String class of the underlying host system (and may be identified with it).”).  Scala strings are Java strings, and if some behavior of Java strings is just not quite Scala-like, that’s too bad.  Yuck.

AC adapter not included

Here's where I think the Python comparison makes Scala look bad.  The Python slogan “batteries included” refers to the standard library, which is full of useful stuff.  Scala native libraries are mostly collections and utilities.  Scala has direct access to all of the Java standard library, but there are no standard wrappers to make it easy to use those Java classes from Scala, and the Java standard libraries lack a lot of useful things that can be found in the Python library.

JVM crap

Thanks to the way Java handles generics, Scala has to live with the JVM's type erasure.  That's a nuisance, and it makes some code clumsier than it ought to be.

Scala has to live with Java's exception and class/interface models, too.  The former isn't hopeless, although interoperability means dealing with checked exceptions.  The class/interface model means that the mapping between Scala types and Java types can be surprising.

That Darn @!

There really are only two things that @ is used for (I think), annotations and pattern matching, but somehow the many different ways this can manifest just makes me worry every time I see an @.  Annotations on classes, methods and variables are simple enough, but annotations on types and expressions get a little confusing.  The annotation is not always where you might expect it to be, and it's easy to lose track of what's going on.  The cognitive load for distinguishing an annotation from a patten match type constraint and figuring out what exactly the annotation applies to is higher than I would like.

Things I’m Just Not Sure About

Implicit magic

Scala's mechanisms for implicit parameters can do amazing things.  Implicit parameters are the way that generic methods on collections can return new collections of the right type, for example.  And the “pimp my type” pattern (using implicits) makes it easy to add behavior to an existing implementation.

Implicits are pretty carefully controlled: the compiler will complain about ambiguity, so the risk of surprises is fairly low (but not zero).  But implicits are also a temptation to hide parameters that could be explicit—just to reduce the number of characters that have to be typed (silly) or read (maybe good, maybe bad).  My bias is with Python: “Explicit is better than implicit.”

Syntax follies

Scala will let you define your own special-character sequences as names.  This can and does lead to people doing things that, in the guise of “domain specific languages,” are really just silly.  But if I ever need to, I can define the UFO operator as <-^->.

I think the rules for infix syntax are the same for both special-character names and ordinary names (+ or plus).  But the rules for when parentheses are mandatory are not easily internalized, especially for parameters that are functions.  It is not always clear why sometimes parentheses are necessary, sometimes braces are sufficient, and (maybe?) sometimes you need both.

XML baked in

There seems to be consensus that this was probably not the best idea.

Churn

Scala is very much a living language, and the rate of change is kind of high.  I'm not sure whether I think that is bad or not.  It will certainly depend on your use case.

Random Stuff

The Scala meaning of yield is quite different from the Python meaning of yield, since Scala doesn’t have generators and Python has different for-comprehension syntax.  That threw me at first.

I guess functional programming gurus have no problem with it, but I always have to translate flatMap into the equivalent nested for comprehension, which makes code using flatMap a little harder to read.

Companion objects are pretty handy, but it’s easy to confuse the companion object’s apply method with a constructor.  (The classic example is the difference between Array(0) and new Array(0).)

Also, nothing actually guarantees that the unapply method used in pattern matching is consistent with the apply method (or case class constructor).  That’s an opportunity for confusion.

Summary (or “TL;DR” if you prefer)

Scala is actually pretty cool.  It has its warts, like any programming language, but there is a lot there that feels right.  It’s too bad it doesn’t have more traction.