Monday, August 31, 2015

Thoughts on Clojure, Page Two

WTF? You didn't mention Clojure at all!

Perhaps after my prior “six months” post people were expecting more vitriol. Or adulation. Sorry to disappoint.

I really can't find much to say about the language itself, positive or negative. I'm not turned off by the number of parentheses, because I simply see them as doing double duty; “C-family” languages split the job with braces. I find prefix notation somewhat hard to read, coming from an English-language subject-verb-object background, but that's easily solved by making lots of helper functions. Parameter ordering in the standard library seems haphazard, and there are some edges to the sequence abstraction that I stumble over. But overall, meh.

So rather than write about the language itself, I decided to write about attitudes. I was prompted by the book that I used to learn the language. Overall, I think it's a good book, and I'd recommend it to someone learning the language (and I still use it). But the authors are a little too focused on the word “concise.” At one point (table 9-1), they compare Java to Clojure and make the claim that Clojure is “certainly more concise” than Java, even though almost all of their examples use the same characters — just re-arranged. Examples like this perpetuate the stereotype of the “smug LISP weenie.”

I can understand why LISP was considered revolutionary back in the 1970s and 1980s. There was a generation of programmers that learned BASIC on a DEC mini, and I can imagine them going off to college and seeing a whole new world: scoped variables! first-class functions! dynamic dispatch! structuring a program as list transformations!

Actually, I don't have to imagine that, I was part of that generation. I happened to go to a college that focused on strong typing and provably correct programs, with PL/1 as the implementation language. Which is one reason why my degree, when it finally came, was in History and not Computer Science.

But that was then, this is now, and most mainstream languages have features that were once revolutionary. Indeed, one of the reasons that Clojure exists is that it leverages the JVM, which natively supports the garbage collection and dynamic dispatch that underly any LISP implementation. But those same features are also available for Java.

So, to proclaim Clojure “better” due to its language constructs seems to me as misguided as proclaiming HP calculators better because they had RPN.

That said, I think there is something revolutionary happening in the Clojure community, and that's the view that program state must be considered in relation to time. That, to me, has far more relevance than whether you use braces or parentheses, or where you put the operator.


Rüdiger Möller said...

Good read.
I always wonder how much some programmers are tied to syntax instead of underlying concepts .. does not mean elegant syntax can make a difference, though.

Unknown said...

It's difficult to look at Lisp syntax in isolation - it's a key part of the language's appeal, but only because it works in concert with other parts of the language's overall design. The textual representation of the language isn't so much a deliberate syntax as it is a serialization of the AST data structures that are themselves the syntax. (Some dialects of Lisp even go so far as to specify the syntax directly in terms of the data structures. You can see some of that here:

if you think about it in terms of traditional (dragon book) compiler design, compilers have a front end that loads textual code into a data structure (AST), runs that data structure through a number of transformations, and ultimately generates something the machine can turn into useful work. Usually, the underlying AST is private to the compiler's implementation, and the mapping between source text and AST is not at all visible. In an environment like this, it's possible to write source text transformations, but not necessarily easy. This paper shows one way to accomplish transformations in javac using JRS-269)

Where Lisp's syntax comes in is that the language's parser is much simpler than usual and the connection between the source text and the (first) AST used by the compiler is much easier to understand. This makes the textual syntax less obvious and the underlying AST more obvious/useful. Whether or not this is a worthwhile trade off is in the eye of the beholder, but I do think the approach imposes learning-time costs early and only pays off further down the learning curve.

In a way, your comparison to RPN calculators (and FORTH) is very apt. The simpler implementations of all three tools impose initial complexity with the pay off that more of the implementation is easily accessible to the user. They are all definitely long-term plays for people willing to adapt themselves more than usual to the needs of the machine.

One other final note is that when I say 'simpler implementations', I'm really referring to the lexical and syntactic analysis phases of the compiler. Further along the compilation pipeline, good Lisp implementations are not simple at all (which is true for any serious language these days).

kdgregory said...

My underlying philosophy is that all things should have a purpose, and the way that the "thing" achieves its purpose should be considered in light of alternatives.

So, simply saying that LISP syntax is good because it's easy to transform via macros is not sufficient. I believe that it's necessary to go one step further, and consider the purposes to which macros can be put, and consider them in comparison with alternatives.

For example, Clojure uses macros to provide lazy evaluation. That's *one* approach to the problem; others include "lazy always" and using metadata to identify lazy parameters.

This does *not* mean that I'm against macros per-se, or even the uses to which Clojure puts macros. Or to which various developers might put macros.

But, to restate the core of this post, in my opinion focusing on the syntactical constructs is too narrow a view.