I've been working on a Lisp for the last couple of years. There's one major downside to reader macros / syntax extension: Your editor probably won't understand it, and in extreme cases won't know how to indent or read it.

Note that Lisp macros do not suffer this problem because it still uses the standard `'(,"foo") syntax. (In fact it's somewhat remarkable that Lisp syntax can be described in shorthand so compactly.)

Reader macros are a different beast. Let's say you want to add support for [] as indexing. You can do this:

> (let l '(a b c)
l[1])
"b"

And this:

> (let l '(a (b) c)
l[1][0])
"b"

And since the reader is under our control, it's easy to use a dynamic expression as the index:

> (let (l '(a (b) c)
i 0)
l[(+ i 1)][i])
"b"

If you try this, you'll find most Lisp editors work okay-ish with it.

Now let's say you want to be able to do this:

> (if x) {
(print "hello")
}

This is entirely possible since we control the reader. The rule is, if the following expression is a curly-body, it gets converted into `(do ,@body) and inserted as the last argument of the previous expression. So it becomes:

> (if x (do (print "hello")))

However, this fails hard inside of standard Lisp editors. You'll lose indentation right away. Nothing understands curly notation. For example if you turn on Clojure mode and try to auto-indent, here's what you get:

> (if x) {
(print "hello")
}

The alternative curly style is just as broken:

> (if x)
{
(print "hello")
}

So if you expose this power to users, suddenly you have an editor problem: How can the editor know what to do with tokens, short of running the program?

Even in Emacs, which is Lisp-based, it's not really possible to execute other Lisp variants. (I actually got both Scheme and Arc to work inside of emacs, which was amusing. No external programs; literally scheme, but running in elisp.)

But even if it were possible, you wouldn't want to. An infinite loop would freeze your editor. Not to mention it should always be safe to open a file for editing, yet this breaks that safety guarantee.

I think the solution is something like:

> (define-reader ("{" s curly-body)
...)

Within the define-reader block, `s` is the stream, and you can read tokens from it and return a result, or stuff the result into the previous token's last argument, or throw an error, etc. And since it ends with "curly-body", Lisp editors can be designed to detect this token and turn on "curly-body syntax" for any file that imports it.

It's worth it. Would you believe this is Lisp?

> require("fs").existsSync("/tmp")
true

It's equivalent to:

> ((get (require "fs") "existsSync") "/tmp")

But ten billion times more readable. And Lisp!

I have no idea why Lisp doesn't let you call functions like

foo(1 2 3)

instead of

(foo 1 2 3)

It's an unambiguous transformation in every case. Nobody smashes an atom into a left-paren, ever. If there's an atom followed by a left-paren, or a right-paren left-paren combo, it means you want to call it as a function.

It leads to some amusing situations:

(list 1 2 3)
list(1 2 3)

But there's an easy solution: If it looks confusing, don't write it that way. Just like every other language feature.

Even though Lisp is such an old language, I feel like we've barely scratched the surface. Clojure was a promising demo of what's possible. It's time for the next step.