Complex Syntax

Summary: Lisps are revered for their simple syntax, but parens are complex. They complect function calls and macro calls, which have drastically different semantics.

One of the problems that people have with Lisps is that they hate the parentheses. Clojure does a pretty good job of minimizing unnecessary parens and giving them a much clearer meaning. But there's a deeper problem that people express all the time when they're first learning. It's frustrating to watch people struggle with it, because it's not their fault. It's a problem with Lisps in general.

Parens in all Lisps I've seen, including Clojure, are complex. I'm not using the word lightly. Parens complect two similar but distinct ideas: macro application and function application.

Macros and functions are obviously different. Macros are expanded at a time just before compilation called "macro-expansion time". They typically cannot be accessed at runtime. Functions, on the other hand, are applied at runtime. And they are first-class, meaning they are runtime values. In addition, the calling semantics are different. Macros are call-by-name. The code of each gets passed unevaluated. Functions are call-by-value. Functions and macros are two distinct species.

However, despite being distinct semantics, the syntax for calling the two is identical. Parens complect applying macros with applying functions. Beginners trip up on this all the time. Their head is already spinning from the notion that some of the things they are learning are macros, called at compile time. Now add on top that the syntax of the language does not help one bit in distinguishing macro calls from function calls. You just have to memorize what's a macro and what's a function.

We learned in The Next 700 Programming Languages that our syntax should serve to elucidate the semantics. Lisp just fails at this pretty hard. The only consolation is that you actually can remember, with time and experience, what's a macro and what's a function. Every Lisp programmer is proof of that.

A simple solution would be to have a weird syntax for calling macros. You know, instead of parens, you use something else. Something that distinguishes the two to decomplect them. This would have broad and deep implications for the language that I cannot begin to fathom.

The takeaway for the beginner is that, sorry, Clojure won't help you much with this, but it's very important to know what's a macro and what's a function. You just have to keep track in your head. If you're not sure, you can call clojure.repl/doc^1 on any symbol. If it names a macro, it will tell you.

So, there you have it. Lisps complect function calls and macro calls, which have drastically different semantics, using the same notation. Common Lisp and Scheme use parens for much more than that, making the syntax complex and context-dependent^2. Clojure removes a lot of those parens, replacing them with square braces or removing them altogether. However, the complexity of macro and function calls remains.

Despite this, Clojure is still a great language! If you'd like to learn Clojure, I have to recommend the LispCast Introduction to Clojure video series.

  1. That's a macro.
  2. For instance, inside of a let, parens take on the meaning of grouping the bindings and also grouping the variable with its value.