The Weird and Interesting Lisp Syntax

As I go through the course Programming Languages Part B, which teaches programming concepts on the language Racket, let me reflect on the strange syntax of Lisp (on which Racket is based).

lisp-cycle

(Image source: https://xkcd.com/297/)

Lisp? Racket?

I see that in a world full of Python and JavaScript, Racket and other Lisp “dialects” are kind of obscure nowadays. Racket is a (mainly) functional language, but I am here not to talk about its paradigm.

Racket’s syntax is all about parentheses! Something like simple arithmetic should be written like this in any common language:

1 + 2 * 3 ^ 4

That would be actually written in Racket like this:

(+ 1 (* 2 (expt 3 4)))

This does what you’d expect from the first line of code, and as we learn in any basic programming course, nesting works from the inside out: expt is run first to give us 3 to the 4 which is then multiplied by 2 then added to 1.

That’s too many parentheses already! But you should notice that the order of symbols is different. This is because the arithmetic operators are prefix functions which are called by putting them inside parentheses: (function arg1 arg2 ...) which is a bit different. Notice that this makes parentheses an obligatory part of the syntax and not just to group things together.

Let’s take things up a level and see a piece of code that corresponds to a variable definition and multi-branch if expression that uses that variable.

(define x 10)
(cond
  ((< x 10) "x is smaller than 10")
  ((> x 10) "x is greater than 10")
  (#t "x is exactly 10"))

Again, the parentheses nest everything. Even variable definitions are done with functions inside parentheses. You can see the form (cond (b1 e1) (b2 e2) ...) in which bi is a boolean or an expression that evaluates to a boolean, and ei is the expression that is run when a branch is true (all of which are strings here).

Actually, just for better readability, any pair of parentheses can be replaced by square brackets. This is commonly used to enclose cond branches. So, we can rewrite the previous expression like this (I also added whitespace to make it look like a table):

(cond
  [(< x 10) "x is smaller than 10"]
  [(> x 10) "x is greater than 10"]
  [#t       "x is exactly 10"])

As a final example, here is a function definition that uses map which takes an anonymous function and a list of strings. The anonymous functions appends a suffix at the end of each string.

Note: If you don’t know about map and anonymous functions, check out my map tutorial in which I explain these concepts and their applications in R.

(define (string-append-map xs suffix)
  (map (lambda (x) (string-append x suffix)) xs))

Why the syntax is interesting

Having parentheses for everything makes the syntax initially confusing. There are no (infix) operators or any other forms of special syntax that make pieces of code more or less distinguishable. But there is actually a couple of benefits to that syntax.

It is minimal

I am going to paraphrase the course instructor, Dan Grossman, here: the syntax is so simple it can fit on one slide. Instead of having special syntax for every construct (e.g. variable/function definitions, if/else statements, function calls, etc.), Racket has most of its constructs as functions or special forms that have almost identical syntax.

It is unambiguous

In the arithmetic code written earlier, you need to remember how the operations are ordered to code it correctly. A lot of syntax rules in programming languages are like this. For example, the pipe operator in R might not work the way you expect it when there are other operators before it. (Try to work out which of e2 or e3 is equivalent to e1 in this piece of code.)

1 + 2 %>% exp()    # e1
1 + (2 %>% exp())  # e2
(1 + 2) %>% exp()  # e3

In Racket, however, the simple syntax leads to having only one way to do things: every operator/function is prefix and you can nest them as deep as you want because everything is already enclosed in parentheses.


Remember that opinions towards syntax are just opinions: they are subjective. I do like that syntax and enjoy programming with it in the course. I am not sure if I would ever get to use a Lisp dialect in a “real” application, but that would be fun.

Tags: