lots of people complain about Lisp syntax -- they find it too weird and verbose, they call LISP "Lots of Irritating Silly Parentheses"; and sometimes they even pop up with proposals to "fix Lisp" on comp.lang.lisp -- "Lisp is sort of cool, but this syntax... let me show you my great ideas."
on the other hand, most lispers (and I among them) actually love s-expression syntax.
who is right here? are syntax preferences a subjective thing, or one can decide which is better quite in an (more-or-less) objective way? or, perhaps, that's just a matter of taste and custom?
i've got a good example today.. i'm using Parenscript -- cool Common Lisp library that automatically generates JavaScript from Lisp-like syntax -- and i've wrote a function that caches document.getElementById results (that makes sence for dumb browsers like IE):
(defun my-element-by-id (cache id) (return (or (slot-value cache id) (setf (slot-value cache id) (document.get-element-by-id id)))))
this is a common Lisp idiom to use (or place (setf place value))
construct for a things like caches, and sometimes people even make a macro for it, i.e. (get-or-init place value)
. but after writing this i had some concerns -- while that works fine in Lisp, will Parenscript compiler be able to handle such trick? after all, Parenscript is quite simple, and it might not know such complicated stuff. so i've checked how this compiles to JavaScript:
function myElementById(cache, id) { return cache[id] || cache[id] = document.getElementById(id); };
well.. it did not look plainly wrong to me, but it looked more than suspictious, so i've decided to check this in browser. and indeed, Firefox said SyntaxError: invalid assignment left-hand side
. it turned out that ||
operator's priority is higher than priority of =
, so it was interpreted like (cache[id] || cache[id]) = document.getElementById(id)
, which is obviously wrong. adding parentheses in correct way:
function myElementById(cache, id) { return cache[id] || (cache[id] = document.getElementById(id)); };
fixed the issue.
so this gives us clue about syntax characteristics -- with Lisp-style uniform prefix syntax you're always sure that compiler understands you correctly, while with C-style syntax you're not so certain. so you can easily see why Lispers actually love s-expressions -- such syntax gives them a feeling of confidence, it is sort of a joy when you write the thing and you know it is correct. and vice-versa: if you're not confident, you're feeling somewhat distracted, even if you've wrote it right!
one might ask -- can't you just learn JavaScript and be confident?
theoretically you can, but it's not so easy. first of all, why bother at all? how can you say that JavaScript syntax is superior if it requires you to memorize arcane precedence rules? those rules are definitely not a simple thing, if even Parenscript compiler authors got them wrong; and so i'd say chances you'll mess something in some complex case are pretty high. as i've noted above, it's just possibility of an error what bugs you, not errors themselves. so even if you get most cases right, it doesn't really help... also, knowledge of operator precedence table alone is not enough -- when you're programming, you need to understand syntax on subconciousness level to do it quickly and effectively. not that it's impossible -- somehow i felt that something is wrong with that JS expression, but it's hard to reliably know these precedence rules.
another reason, there are different languages with different syntax quirks. for example, in PHP expression $cache[$id] || $cache[$id] = 5
-- i guess (PHP has no formal specification, so one can only guess anyway) =
operator has higher priority than ||
. (but operator ||
has other semantics -- it coerces value to boolean, so it won't work as expected). this precedence rule is pretty weird if you consider such snippet:
$a = ''; $b = $a or 'b'; echo $b; echo htmlentities($a or 'b');
you might think that expression $a or 'b'
is same thing in both cases, and it should yield same result (1, as it coerces to boolean too). but actually first expression is processed like this ($b = $a) or 'b';
, so $b
would be empty.
thus, if you use more than one language with "familiar" C-style syntax (PHP + JS combination is quite popular), you're even in worse situation as it gets more difficult to remember what languages have what precedence rules and quirks. with inconsistent PHP, freaky Perl and uber-complex C++ it gets out of control.
so (i hope) it's now easy to see why people appreciate uniform Lisp syntax, and why they make translators like Parenscript -- to make everything uniform and guard themselves from syntax quirks. unfortunately, it doesn't work perfectly all the time, but i think still better than pure JavaScript.
but is this syntax stuff really important? i'm pretty sure most programmer folks simply do not care: they prefer using simplier programming constructs (such as temporary variables) -- that makes code more verbose, but more "safe"; and they are not confident with their code anyway -- they always test what they write, and if it doesn't work, they look for a workaround.
also i suspect there are hardcore folks who actually know all the syntax rules, and it is not a problem for them. haven't seen them in a real life, though.
so, my conclusion is -- Lisp syntax is quite objectively more robust and less error prone than, um, most other syntaxes out there, and people who find this qualities being important are likely to find it great. (conclusion was updated after debates on reddit)
P.S.: oops, this ended being a rant, while i just inteded to give a small example..
P.P.S.: if you think such JS code is atypic, here's what i've found in some JS-related blog:
function $(id){ return !cache[id]?cache[id] = document.getElementById(id):cache[id]; };
it's even more complex, and more verbose same time.
UPDATE:it appears some people understood this as if it is all about operator precedence rules -- no, actually it's not, it was just my example. there's much more about complex syntax rules, for example, C++ is notoriously difficuly.
Comments
saving a few keystrokes at code creation just isn't on the top of my list.
i'm not saying that the lisp approach successfully address this issue, mind you, but i don't think it's never been a significant issue in my code or code I've maintained (which is lots).
function memo(f) {
f.memo = f.memo || {};
return function (x) {
return (x in f.memo)? f.memo[x] : f.memo[x] = f(x);
};
}
I compared this solution to an OCaml one, and although my comparison wasn't that "objective", I believe that javascript's syntax can be quite beautiful to. You can read more about that in my blog post
Basically, just slap parens around the operands and life is good.
Its much easier to read (((2*3)+(2*3))+(5*5)) than (2*3+2*3)+5*5. I mean, I might end up with 65 instead of 37 if I didn't know the precedence rules!
No, I'm happy to learn a couple of precedence rules instead of being verbose all the time. I don't think your argument holds.
i had to draw parse trees for my sister to understand solving equations (with logarithms, iirc), i'm no kidding! not seeing parentheses she could not understand why you can move one isolated part to the right hand side while you can't another.
I hate Forth Syntax but I saw really awesome stuff performed in it.
Anyway, what happens if you compare lisp syntax to ruby syntax? Ruby syntax beats javascript syntax hands down with ease - wearing a blindfold.
i don't get Forth syntax (or lack of thereof) either, but at least it is simple and has no quirks.
on the other hand, as far as i know Ruby has quite arcane syntax, and while it's quite powerful, it's not a language i'd like to program in.
Overall, I think that the precedence rules argument is not a strong enough reason by itself, as I think okke's sarcasm clearly shows.
I'm attracted to Haskell because it has such a mathematical notation--proof notation, and not just formula notation--but ultimately, I'm attracted to Lisp because of its macro system, and because of its dynamic nature (I've used a lot of Python, and C++/Java before that; I've always appreciated the dynamic nature of Python :-).