Skip to main content

Lisp syntax is great!

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

And another great thing about Lisp syntax is emacs+paredit, which allow editing code in somewhat structured way, rather than having lines as primary structure feature.
Doug said…
fwiw, i've never run into operator precedence issues in my code because I use lots of parens to make things clear (as i would when writing a mathematical formula). since the code will be read by many other programmers over its life i tend to try and make it as readable as possible.

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).
noumeno said…
Hi. I kind of wrote a post about memoization in javascript, and the syntax didn't turn out to be "ugly" (from my subjective point of view):
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
eadmund said…
So, did you submit a patch for Parenscript to avoid the bug in the future?

Basically, just slap parens around the operands and life is good.
killersorm said…
noumeno: yep, IMHO JavaScript has relatively nice syntax if we compare it to Ocaml. maybe it's just me, but parsing all those pattern mathings and let .. ins is PITA.
killersorm said…
eadmund: no, but i've reported bug to a mailing list. i guess guys there can fix this faster than i can, if they consider this a bug, or course..
Okke said…
Wow, you are totally right. Mathematicians are crazy as well. Why don't they just use parenthesis instead of remembering that strange order?

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.
killersorm said…
if you ever tried teaching people math you probably know that considerable effor (perhaps most of effort) goes into teaching how to parse damn notation.

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.
shevy said…
Well, syntax issue is overrated. I think how capable a language is should not be restricted ENTIRELY down to syntax.

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.
sysprv said…
As for me, I don't bother learning operator precedence of infix languages. I just use parentheses to explicitly say what I mean ;))
killersorm said…
shevegen: indeed syntax is not that important, but still people have their preferences.

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.
killersorm said…
sysprv: even if you insert parentheses in your own code, you might still need a precedence table when you'll read some other's code
shusaku27 said…
I love Lisp syntax, but primarily for different reasons than stated here. Not having to worry about precedence rules is a nice side-benefit, but the real beauty of Lisp syntax is the blurring of the line between what is data and what is code, allowing you to do all sorts of powerful things (like macros). I also like that special syntax is not used for things that are actually the same. For example, the mathematical operators are simply functions that are applied in the same way as any function is.

Overall, I think that the precedence rules argument is not a strong enough reason by itself, as I think okke's sarcasm clearly shows.
Alphy said…
I'm a mathematician just beginning to try to learn Lisp; I just recently (in the last few months) discovered Haskell, and in the process, discovered just why Lisp is so special. Both are highly mathematical languages!

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 :-).

Popular posts from this blog

Lisp web tutorial?

"PHP vs. Lisp: Unfortunately, it's true..." article initiated quite active discussion on reddit , one fellow asking : Can someone post a tutorial for taking a clean install of Ubuntu (or windows or anything) to finish with serving a basic CRUD application using lisp? Maybe a TODO list with entires consisting of: incomplete/complete boolean, due date, subject, body? actually i had an impression that there are more than enough such tutorials, but as nobody replied i've tried finding one myself, starting with Hunchentoot tutorials. surprisingly, none of them covered a short path from clean OS install to working examples. neither i've found my ABCL-web  tutorial suitable for this, so i decided to try myself.  my idea was that Linux distros like Debian and Ubuntu contain a lot of Lisp packages, and it should be fairly easy to install them, as it automatically manages dependencies etc. i've decided to try Hunchentoot -- i'm not using it myself, but it's k...

out-of-memory: a sad case

the problem.. while Turing machine's tape is infinite, all real world programs are running within some resource constraints -- there is no such thing as infinite memory. for some programs that ain't a problem -- amount of memory needed by the algoritm can bee known beforehands, at programming time. but for most real world applications memory requirements are not known until run time, and sometimes it is very hard to predict how much does it need. obvious example is an application that allocates memory according to a user input -- for example, an image editor asks user for a dimension of an image he'd like to create. it needs to allocate an array in memory for the image (to have a fast access), so when dimensions exceed possible bounds, good-behaving application should notify user -- and user can either reduce image size or, perhaps, upgrade his machine, if he really wants to work with large images. while it is fairly easy to implement such functionality on a single-task O...