On Lisp Syntax

stereobooster - Dec 27 '20 - - Dev Community

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.

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.

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)
Enter fullscreen mode Exit fullscreen mode

in Lisp will look like this:

(+ 1 2)
(f 5)
Enter fullscreen mode Exit fullscreen mode

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 read hard to write
change syntax 1 2
change editor 3 4

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)
Enter fullscreen mode Exit fullscreen mode

Example in Racket:

(define [gcd a b]
 (if (zero? b)
 a
 (gcd b (modulo a b))))
Enter fullscreen mode Exit fullscreen mode

Let’s dive into the problem

Prefix vs infix notation

Prefix notation

(+ 1 2)
Enter fullscreen mode Exit fullscreen mode

Infix notation

1 + 2
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

Parentheses outside

This is a trivial transformation:

plus(1 2)

Enter fullscreen mode Exit fullscreen mode

We need simply move the first symbol inside the list:

(plus 1 2)
Enter fullscreen mode Exit fullscreen mode

Too many parentheses

Reduce “optional” parentheses, for eaxmple traditional Lisp:

(cond
 ((evenp a) a)
 (t 17))
Enter fullscreen mode Exit fullscreen mode

vs Clojure (it removes () around cases)

(cond
 (even? a) a
 true a)
Enter fullscreen mode Exit fullscreen mode

Vary (alternate) parentheses, for eaxmple traditional Lisp:

(let ((a 1)
 (b 2)
 (c 3))
 (+ a b c))
Enter fullscreen mode Exit fullscreen mode

vs Racket

(let ([a 1]
 [b 2]
 [c 3])
 (+ a b c))
Enter fullscreen mode Exit fullscreen mode

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

[].each do |x|
 puts x
end
# or
[].each { |x| puts x }
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .