What makes Lisp macros so intuitive is not the prefix notation, it's the homoiconicity of the language. Writing a macro is just a list operation, where the familiar map / filter / reduce applies.
What makes Lisp macros so intuitive is not the prefix notation, it's the homoiconicity of the language. Writing a macro is just a list operation, where the familiar map / filter / reduce applies.
The culture is the ultimate "reality distortion field." Prefix notation would be seen as intuitive, if that were culturally established. We'd see something like PEMDAS as arbitrary and silly.
Just look at how much content there is around PEMDAS and interpretation of math problems. Clearly, it really isn't "intuitive." We just have this enshrined in the culture. (That said, one of the biggest UX mistakes the designers of Smalltalk made, was to eschew operator precedence!)
Subjective, I know, but I think (+ 1 2 3 4) is way nicer than (1 + 2 + 3 + 4).
4 3 2 1 + + +
If I have to write 1 + 2 * 5 - 3, I'm not going to "start" with *.
I will type:
(+ 1 2<-<-(* ^E 5) <-<-<-<-<-<-<-(- ^E 3))
It indicative of how I'm thinking about it (regardless of how the navigation is done).I just have a lot of those starts and stops when converting infix to prefix in my tiny Piglet brain.
That said, as a general rule, yea, I like (+ 1 2 3 4) and its ilk. It takes a bit of exploration to read. For example, I need to transit the entire list of 1 2 3 4 to understand all that is being added, which can be more difficult with longer expressions. But that can be mitigated with formatting:
(+ (this that)
(func zzz)
(if (< v q) 1 3))
(Function conditions are also fun to intermix into things!)I think just as a rule, I find formatting more important in general with s-expr systems than algolesque systems.
That's a great example of an expression that can be ambiguous, but not with prefix/suffix notation.
Most people that write lisp-like code use an editor that helps with s-exp editing (like paredit), so that isn't really a significant issue. In fact, I think it is faster to write s-exp code than algol-likes once you've become accustomed.
I agree that formatting is very important w/ parens langs, but there are many languages that consider formatting a high concern.
I would also argue that reading code is harder than writing it. Optimizations that speed up writing code is less interesting to me than ones the help reading/understanding/making assertions easier about code.
Finally, infix notation is available in lisp/scheme, it's just a macro away (but seriously, don't).
When using "curly brace languages", what I really miss is structural editing. I often deal with tree-like structures in any language, and being unable to simply cut a node, move my cursor to the start/end of a node, nest something, etc. is really inconvenient. These are operations that take me at most a second when using Emacs with smartparens. In JSX, for example, these one-second operations need me to imitate smartparens' behavior by hand then run Prettier since indenting by hand is an even bigger waste of time. Transforming e.g.
<Foo>
<Bar>
<Baz />
</Bar>
</Foo>
to <Foo>
<Bar />
<Baz />
</Foo>
takes *seconds* even though the equivalent operation with S-expressions is a single keybind in Emacs.The numerical operators, with the Boolean operators, negation, comparisons, maybe implicit type conversions as well. Maybe && as separate from &. Maybe user defined operators with different precedence to the builtin ones.
Maybe the large precedence table you have to keep in working memory to use the infix conventions is still simpler. Maybe even when there's a style guide saying to put lots of parentheses in as well. It starts to be difficult to fit a definition of simple to the observations though.
Mine, neither, for maths.
But for everything else, we’re both already used to prefix:
fprintf(STDOUT, "foo %d", 3);
is prefix, just as much as this is: (format *standard-output* "foo ~d" 3)
And it turns out that the advantages of a single homoiconic notation are so compelling that it’s worth a little bit of mild ugliness when dealing with maths.Now if we want to handle compounded expression, you need either a given constant finite number of argument, or have a reserved word to mark grouping, much like `)`. A bit like stack based programming languages.
I keep trying to pin point the psychology of it. Cause even as a kid, I was more attracted toward HP calcs even though I never heard of lisp at this point, but RPL felt like infinite magic. And of course when I ran into emacs, I felt the same strange feeling.. there's something.
target.method(argument, another)
Here, the operation is `method` and the operands are `target`, `argument`, and `another. Note that the operation appears in the middle of the operands.Even then, it's still not a prefix operation. It's postfix.
Things like this are mentioned, in some detail, in the O'Reilly book Python in a Nutshell, IIRC, in the first edition, which is what I have.
Couldn't quite wrap my head around all the material in that chapter, TBH.
But I still like to read about such stuff, and I do understand bits and pieces of it.
Note that the "(" in (a, b) is also an operator. (There is some special parsing because otherwise you could do things like args = (1, 2, b=3); target.method args.)
As to whether <thing from target.method><thing from (a, b)> is postfix, I'm not sure how you get there. Yes, there's an implicit funcall at the end, but there's something similar with (+ a b) or ((. target method) (arglist a b)) and we wouldn't call them postfix.
var x = 123
print(-x.abs())
Or even: print(-123.abs())
What do those print? Do they print the same thing?I think that most problematic for prefix notation are not arithmetic operations, but field accessors.
In C you have a->b->c->d, in Lisp it would be (d (c (b a))), which makes you to jump to the center and read it from the inside-out.
The direct translation of the Lisp expression you shared to C would be: d(c(b(a))), which just like the Lisp expression, evaluates from the middle out. Both are fine to read from left to right though.
Not really. -> looks like binary infix operator, but it is really an unary postfix operator (parametrized by field). Because field itself is not a first-class entity in C.
#include <stdio.h>
#include <stdlib.h>
struct foo {
int a;
};
int main() {
struct foo *f = malloc(sizeof(struct foo));
f->a = 5;
printf("%d\n", f->b);
free(f);
return 0;
}
test.c:11:21: error: no member named 'b' in 'struct foo'
11 | printf("%d\n", f->b);
| ~ ^
1 error generated.
What do you mean when you say fields are not first class in C?Yeah, and IMHO most operations in most languages suffer from being mostly prefix. Yes, LISP is not that much worse, but doubling down on the bad part doesn't exactly recommend it. ¯\_(ツ)_/¯
One of the cool things about Smalltalk is that it consistently makes everything infix.