Skip to main content

On Lisp Syntax

··

Why Lisp? #

Lisp is a micro language - it is trivial to implement basic Lisp interpreter. Lisp is a source of many discoveries in computer science, for example, garbage collection, tail call optimization, macros, etc. Lisp is used for teaching - SICP, MIT 6.001, How to design programs. Lisp is used for production - Clojure, Clasp. Lisp is used for research - Scheme, Racket.

It is a majestic language, except for the syntax.

Note: when I say Lisp I mean any member of the Lisp family, for example, Common Lisp, Scheme, Racket, Clojure, etc. - all languages that use S-expressions. They all are different, but for this article, it doesn’t matter.

What is the problem? #

There is nothing specifically bad about Lisp syntax. There are a lot of people who write Lisp in a day to day job. The problem is rather the first impression. When you see Lisp for the first time it most likely will seem to you very strange, because it doesn’t look like anything you’ve seen before. For example, math (that you have learned in school):

1 + 2
f(5)

in Lisp will look like this:

(+ 1 2)
(f 5)

The difference is not dramatic it is an easy mapping from one concept to another, but it seems strange and very obvious why would you want to invest your time in adopting that syntax.

It is somewhat similar to the Vim editor. It doesn’t work like many other editors, you need to invest time to memorize short cuts and learning about modes. But I would say adopting Lisp syntax is easier than learning Vim…

What is the solution? #

I think there is no way to convince all people that Lisp syntax is ok. This ship sailed - all mainstream languages look like C (more or less).

This problem has several dimensions:

  • It is hard to read / it is hard to write
  • Solve by changing syntax (notation) / solve by changing editor (media)
hard to readhard to write
change syntax12
change editor34

1: this is what I want to talk about 2: I have no good examples 3: dim parentheses, rainbow brackets, etc 4. parinfer, paredit, etc. See Inspiring a future Clojure editor with forgotten Lisp UX - Shaun Lebron

I want to explore a potential solution for the 1-st quadrant. What if we can change syntax (but not too much), so it becomes more appealing to a wider audience - internally it would be compiled down to S-expressions, so no other changes for core required, except changing reader and printer. Lisp code is not that far away from traditional languages (like Python) if you remove parentheses.

Example in Python:

def gcd(a, b):
  if b == 0:
    return a
  else:
    return gcd(b, a % b)

Example in Racket:

(define [gcd a b]
  (if (zero? b)
    a
    (gcd b (modulo a b))))

Let’s dive into the problem #

Prefix vs infix notation #

Prefix notation

(+ 1 2)

Infix notation

1 + 2

I don’t think this is a big problem because there is a small number of infix functions: math operations (+, -, *, %, ^), comparisons (>, <, ==, !=), bitwise operators (&, |, >>, <<), logical operators (and, or, xor) and some other less common, for example :: in Haskell (cons). The number of prefix functions is much bigger and there are no complaints about them.

Another problem with infix notation - it requires precedence rules (which are hard to remember) or explicit grouping (for example, in Pony).

For the first iteration let’s use prefix notation for all functions. Using words instead of special symbols may make it less controversial:

plus(1, 2, 3);

Parentheses outside #

This is a trivial transformation:

plus(1 2)

We need simply move the first symbol inside the list:

(plus 1 2)

Too many parentheses #

Reduce “optional” parentheses, for eaxmple traditional Lisp:

(cond
  ((evenp a) a)
  (t 17))

vs Clojure (it removes () around cases)

(cond
  (even? a) a
  true a)

Vary (alternate) parentheses, for eaxmple traditional Lisp:

(let ((a 1)
      (b 2)
      (c 3))
  (+ a b c))

vs Racket

(let ([a 1]
      [b 2]
      [c 3])
  (+ a b c))

Reserved words instead of (), for example in Ruby it is possible to do:

[].each do |x|
  puts x
end
# or
[].each { |x| puts x }

Other possible candidates:

  • do/begin/end
  • try/catch/end
  • if/then/else/end
  • (function arguments)/end

Infer () from indentation (Pythonish syntax), for example, for each indent we can assume (begin and for each dedent ), this way we can organize function bodies without additional parentheses.

Other alternative Lisp syntaxes #

See also #

Read more: Metaprogramming, Rapid Syntax Prototyping Tool