4 ms·
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
by sillysaurus3 9y ago
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.
- aaron-lebo 9y agoThe thing is, lispers don't want more syntax. They've rejected Dylan, sweet-expressions, and a million other experiments. There's a sizable contingent who complain about Clojure using [] instead of all (). So really all you are talking about is making a nice syntax that enables macros, and that doesn't require lisp. See Elixir. If lispers aren't going to adopt extra syntax, your only hope is to attract non-lispers, and then you've got the issue where your lispy language has to be better than existing languages at doing something, and that's hard. Why go to so much trouble for something that's been rejected repeatedly? Macros don't require lisp syntax and that's the main advantage. My own ghetto transpiler converts this: sum = reduce partial(+) [1, 2, 3] sum + 1 println into this: (def sum (partial reduce +)) (println (+ 1 (sum [1 2 3]))) https://github.com/aaron-lebo/prose https://github.com/aaron-lebo/prose It's fun to think about, but not sure the general concept has an audience. edit: Bob Nystrom's lark is one of the cooler non-lispy lisps out there: https://github.com/munificent/lark https://github.com/munificent/lark
- lispm 9y ago> The thing is, lispers don't want more syntax. Lisp already has a lot syntax, but on top of s-expressions. Lispers usually want to keep that two-stage syntax: s-expressions for data and Lisp on top of s-expressions. This enables the simple s-expression data representation and manipulation to be applied to programs, too. Lispers did not really reject the non-s-expression Dylan syntax - it was never developed for them. Apple developed that syntax and Dylan to target non-Lisp developers.
- UIZealot 9y agoSometimes I wonder if making the top level parentheses optional would go some way towards making Lisp more palatable to other programmers. So (def sum (partial reduce +)) (println (+ 1 (sum [1 2 3]))) becomes: def sum (partial reduce +) println (+ 1 (sum [1 2 3]))
- mmalone 9y agoSee https://srfi.schemers.org/srfi-110/srfi-110.html https://srfi.schemers.org/srfi-110/srfi-110.html. No idea why it hasn't shown up in a popular language (or maybe it has and I'm just naive).