While I think Go is my future, I'm about to embark on a project using Scala. So I've spent the last week reading Programming in Scala, by Martin Odersky et al. And I've signed up for the Coursera class Functional Programming Principles in Scala, which starts today. I do have some experience with a functional approach to programming, so it's not a completely foreign land. But I lean strongly toward an object-oriented, message-driven (push) approach to decomposing problems; the functional (pull) approach will take some getting used to.
This is the first of two posts. I'll write the second six months or so from now, after I've spent enough time with the language to feel that I “get it” — or don't. Here, then, are my first impressions, after diving just below the surface.
Things that I like
- Implicit conversions
- I'm not sure that no longer having to call
toString()
explicitly (with a null check) is a reason to use Scala, but it's close. Conversion code is almost always clutter, sometimes quite complex clutter. Being able to move that clutter into a method, and rely on the compiler to invoke the method as needed, is fabulous. - Null is an object
- Java's concept of
null
, and the need to explicitly test for null-ness, is one of the less pleasant things that it inherited from C. It's a leading cause of code clutter, and drives the creation of utility classes that provide null-safe operations. I haven't looked at all the methods that Scala'sNull
provides, butequals()
is enough to make me happy. - Traits
- There's a well-worn adage to “favor composition over inheritance.” This can result in a plethora of function calls that simply call a delegate, violating the adage “don't repeat yourself.” Traits make composition easy, creating delegates and then hiding them behind a curtain of syntactic sugar. I will say that I've looked behind that curtain, and don't think I ever want to find myself debugging them.
- For Comprehensions
- This is a neat feature, which I first saw in Erlang. It makes for a very simple
way to apply filters to a list's elements before taking some other action
on those elements, and I agree that it largely replaces
break
andcontinue
. - Match expressions
- Another feature that has its roots in Erlang (or earlier inference languages). I'm especially impressed by constructor matching, which leverages the immutability guarantees of a “case” class (again, I looked behind the curtain: while I think the code generation could use some work — particularly reuse of local variables — the approach is elegant).
Things that I don't like
- Optional semi-colons
- Are semi-colons really that evil? They certainly aren't hard to type: the key is
right there on the home row (at least on a US keyboard). Yet Scala, like JavaScript,
makes them optional. Of course, the compiler still needs to decide where to end a
statement, so has to have other rules. In the case of Scala, the end of a line is
the end of a statement, unless it doesn't make sense for a statement to end at that
point.
I personally find this rule annoying for complex expressions, which I want to format on multiple lines, with the operator first on the line (so that it's obvious). Scala, however forces me to put the operator on the end of the line, where it's hard to see, because otherwise the compiler thinks the preceding term is the end of the current statement. Perhaps this is a not-so-subtle nudge toward simpler expressions, but there are some cases where a real-world rule has lots of terms; it's just easier to read if the terms are on separate lines, even if they're function calls.
OK, that's just me whining. But, like other languages that attempt to do away with statement separators, Scala sometimes requires you to use them. One example is a variable declaration that precedes a standalone block. Not that you're likely to use a standalone block unless you're demonstrating scoping rules. But if you have to do it sometimes, why add the mental load of remembering when those times are?
- Too many symbolic operators
- This was one of my first impressions of Scala, after seeing a JUG presentation
several years ago. I'm neutral on the subject of operator overloading: it can be
abused, but I've seen enough elegant overloads to think it has value. My issue
with Scala is that any arbitrary string of apparent line noise is an operator.
This creates unnecessary mental load: is “
::
” really better than “concat
”, or “<-
” better than “in
”? I don't think so. - Precedence and associativity rules
- Languages like C++ provide operator overloading: you can create new definitions for existing operators, which follow the rules of the language's grammar. Scala provides operator creation: syntactic sugar that makes arbitrarily-named methods look like they're part of the language. But the precedence and associativity rules that underly this feature are Byzantine to say the least. There's really no excuse for this: if you're going to put programmers in the position of being language designers, you need to give them the tools to do the job properly. In this case, explicit definitions of a function-operator's precedence and associativity..
- Underscores as parameter placeholders
- This is, I think, conciseness taken too far: rather than define the parameters of
a function literal, you use underscores in the function body; each underscore takes
the place of a sequential parameter. Intellectually, I get it. Looking at code
filled with underscores, I don't. And if you want to use the same parameter in
multiple places, you're required to explicitly define it.
Better, in my opinion, would be an explicit parameter syntax. Personally, I like the way the shell does it:
$1
,$2
and so on for individual params,$*
to represent all params.
Things that have me scratching my head
- Primary constructor parameters and their relationship to fields
- Here's the example given by Odersky (I'm copying this from the book, so errors are mine):
class Rational(n: Int, d: Int) { // ... }
This makes perfect sense: by defining the class' member variables as constructor parameters, you save the boilerplate of explicit variable definitions and assignments. But then, a page later, he shows that they're not really fields in the Java sense: it's a compile-time error if you try to access them using another instance of the class:def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d)
Now, if you look in the actual class code, you'll find that these constructor parameters are, in fact, fields in the class. Unless you continue the example, create explicit fields, and don't otherwise use the constructor parameters in your code. For a language that eschews boilerplate, I just don't get it.It is, however, something that I've seen before: Scala's primary constructor looks and acts just like a JavaScript constructor function (which is something that experienced JavaScript programmers don't often use). I can understand the pattern for JavaScript, which doesn't have rigidly-defined classes, but it doesn't make much sense for Scala.
- Not everything is an expression
- I've never liked the separation between statements and expressions, and Scala erodes that
boundary in many places. The
if
expression is one such place, function returns are another. And yet, the boundary remains in several surprising places: awhile
statement is one example, but the truly baffling one is variable assignment. While I don't often use constructs such asx = y = 0
, I do use the Java (and C) idiom of assigning a variable and testing its value (eg:(s = in.readLine()) != null)
). But not in Scala.The only reason that I can imagine is that such behavior is functionally impure: I should use an iteration function that calls another function to process the iterated value, so the language will push me in that direction. I suspect the same reasoning is why
while
doesn't return a value: we shouldn't be usingwhile
.
No comments:
Post a Comment