Hacker News new | past | comments | ask | show | jobs | submit login
On Lisp's Readability and Parenthesis Stacking (gregslepak.posterous.com)
51 points by itistoday on July 7, 2010 | hide | past | favorite | 60 comments



The problem is not the ()))))), it's the 200 lines function which does 20 different things all at once. Split that into smart and readable function.. You identified a problem (Hard to get where that println goes), however, you came with the wrong solution.

But I need to say that you are right about the fact that many lisp code are really ugly.. Maybe it's because when programmers are coding in lisp they considere themselves hackers and don't think about coding cleanly.

"With great powers come great responsibility." Since Lisp is the most powerful language, people should learn to separate abstraction into separate function. I mean, it's not because you have lambda and you don't have to name function that you absolutely need to do it.

Often, for small function, lisp coder tends to put that in a lambda and inline it directly in the bigger function.. which in my opinion is not the way to go.

A friend of mine once told me he really liked lambda because he had difficulties to find good game for function and variable.. by stacking all lambda together, it was way faster and easier for him. The problem with that, of course, is when other programmers will actually try to read that code and understand what it does.

Flame me if you want but when I need to work on someone else project written in lisp code, I always know it will be a pain to understand it. However, when I need to read java or C++ code, even thought the syntax is uglier and more verbose, I know I will have no problem reading it.

So here's a small trick for you hackers:

- Don't be afraid to separate your code into many functions, even if you are using lisp.

- Don't be afraid to use (let) syntax to give a name to portion of codes.. it might be harder to write (finding good variable name is sometime really hard and takes more time), however, it will make the code so much readable.


"- Don't be afraid to separate your code into many functions, even if you are using lisp."

Especially if you are using Lisp.


You could extract a macro to create a ZipEntry, bind it to standard out, and execute some actions that write to the ZipEntry. Then the code starting with .putNextEntry and ending with .closeEntry becomes something like

    (with-zip-entry out "META-INF/plexus/components.xml"
        (xml-emit ...)
This macro should be useful other places where you are writing to zip files. And it should bring the println closer to the start of its containing scope, making it easier to tell at a glance the scope to which it belongs.


Agreed.

Forth programmers tend to chew their code into little digestible nuggets of seven words or less. Meanwhile in Lisp many of us (myself included) still tend to favor the Massive Wall O' Code approach, making us look like third-rate VB programmers in the process.

We have compilers that can open code (inline in C parlance) the subroutines that handle the fiddly bits in your big routine. No reason why these shouldn't be factored out in the name of clarity.


>Maybe it's because when programmers are coding in lisp they considere themselves hackers and don't think about coding cleanly.

I was taught and I consistently read that splitting into smaller functions is absolutely fundamental. I don't know where your programmers learned lisp.


am I mistaken in thinking that part of this problem is a desire to not pollute the namespace since most(all?) lisps are unable to properly privatize functions?


Yes. You can define functions locally with "flet" or "labels" and never touch the global namespace. This alone is enough so that you don't have to resort to lambdas to avoid polluting the global namespace. In addition Common Lisp provides both generic methods and packages, which both provide a means of managing the global namespace, so one should never use lambdas for that purpose.


You can also use (define) inside others (define) if you want private functions. And, because of closures, it makes it extremely powerful and flexible since you don't have to repeat parameters already given in the outer scope.


Perhaps this is true in CL, but Clojure makes it easy to define private functions.

In the case of the particular example he points to it's just a long function because many people contributed to that function over a period of several months.


Thanks! I didn't know about defn-

Time for refactoring :D


"If you feel that parenthesis stacking actually improves the readability of your code, by all means, feel free to do so! However, "everyone else is doing it" is not a good reason to sacrifice the readability of your code."

I think it is a good reason. The community benefits when everyone uses a consistent style. Readability isn't just about being able to read your own code, it's about helping other people read your code.

The accepted style of the community will make sense up to a point; no one wants to make things really hard on themselves, and people gravitate toward things that are comfortable. But it'll also be partly arbitrary and probably not optimal in every way. Part of learning a programming language is learning the idioms and dialects of other programmers.

Using a style no one else in the community uses is like making up your own words in spoken language. People will be able to understand you given some effort, but are they going to bother? If they do bother, they're probably going to be annoyed. Even if your new words are "better".

If you're the only one reading your code, knock yourself out. Otherwise you should probably suck it up and adapt, in my opinion.

"No other language that I'm aware of stacks scope delimiters or has a community where the standard indentation width is two spaces wide, and where sometimes even a single space is considered acceptable."

Idiomatic Ruby uses two space indentation. Maybe because Ruby is often so deeply nested, like Lisp.


Scala also, has a standard two-space indentation.


Meh. Took me all of two seconds to figure out what scope that line was in. Then when I read on and was told its hard, I double-checked by counting parentheses only to find out I was right the first time. I honestly don't know what hes talking about.

As for "line count" not being important, I personally like fitting as much code into one screen as I can (especially since I often code on my netbook). Short functions are important, but even then, nice and compact code is still very important to me. Especially since I'll split code onto their own lines once the line gets more than about 70 or so characters long.

I dunno. Even if line count was unimportant to me, I don't see what the problem is, since I found it easy to spot the nesting.

Anyway, that code is much too complicated. Hes using Clojure (ie, a Lisp), he should make use of the available abstractions to simplify the code. This will, in turn, make it easier still to figure out nesting of arbitrary lines of code.


"Now try the question again, in what function or macro is the highlighted line located in? Was it easier to answer this time?"

No, not really, no. I didn't find it any easier either way.

I also don't buy this fellow's argument that lisp is the only language in which people rely on the editor to help them figure the the scope of expressions. I know that I use the editor when coding in C like languages as well. So I don't think that this argument for trailing parenthesis bears any weight.

The only argument that does make sense to me is that inserting a new expression at the end of a form can be annoying with editors that expect to operate on lines.

Personally, I find "parenthesis stacking" to look nicer, and it certainly has the advantage of being standardized and widely accepted in the Lisp community. One of the advantages of Lisp's syntax is that indentation and formating is so well standardized you can rely on an editor to do it right every time.


I also don't buy this fellow's argument that lisp is the only language in which people rely on the editor to help them figure the the scope of expressions

Figuring out scope can be the least of your worries - I've known python programmers cry when a wayward editor messes up their whitespace. I'm not sure where the author gets the idea that relying on your tools to do their job is some kind of grubby taboo.


It was the "let", right?


Yes.


I thought it was a trick question because it was so obvious.


I've long ignored the Lisp formatting debates because I seem to provide, for myself at least, the only counter-example that many authors are convinced doesn't exist. That is, the traditional Lisp parentheses "hell" is very much readable to me, thank you. More so than the C-indented style.

I use C-M-b and -f to jump around the parentheses but I would do the same with properly indented C code as well. I don't always "see" whether this right paren aligns with the left paren that has 16 or 20 spaces of indentation.

Secondly, the author dismissed the argument of lots of indentation and lone right parens making the code longer, but it's a valid point. I can see the Lisp function in one screenful while the C version would flow outside. And for me, that's even worse.

Third, code always needs mental parsing in your head. Code isn't meant to be merely read, it's meant to be inspected and studied. It's an interactive process and it's not cheating to use an interactive editor to help you with that.


Interestingly enough, his "parenthesis-stacked" C code looks just like Python, which I find a pleasure to read.

There's one difference, though: conventional Python is indented four, not two, spaces. In fact, the main aspect of his "normal" C code that makes it more readable (to me, at least) is his use of four spaces for indentation.

Personally I think the biggest barrier to Lisp readability is too little indentation (two spaces is typical). Looking at his reformatted Lisp, the way I identified the nesting level of his highlighted line was not by looking at trailing parens but by looking at the indentation, which was much wider (and therefore clearer) than standard Lisp.


Highly nested constructs are more common and natural in Lisp. I think that explains the low level of indentation. It also hurts readability compared to imperative languages, where it's common to have long sequences like this:

  do something;
  do something else;
  do something else;
  do something else;
  do something else;
Sequences like that give you long columns of aligned text that make it much easier to judge relative indentation on the screen.

Lisp's readability advantage (if it indeed has one; I'm giving it the benefit of the doubt for now) are at the whole-program level. On the function level, it seems more difficult than imperative languages. It would be more fair, and more to the point, to compare it to functional languages. If you write in an imperative style in Lisp, all the parens line up neatly, and it isn't so hard to read. But who codes that way unless they have to?


Your mention of Python made me wonder if anyone's experimented with an indentation-sensitive syntax for Lisp. It turns out they have.

In this Scheme RFI, "I-expressions" can be freely mixed with S-expressions in source:

http://srfi.schemers.org/srfi-49/srfi-49.html

This argument for I-expressions touches on many of the same points as the OP; e.g., whether source needs to be readable independently of the editor:

http://www.dwheeler.com/readable/retort-lisp-can-be-readable...


If you watch a Lisp-related mailing list, it seems like once a week someone brings up the topic "hey, what if I replaced the parens with significant whitespace?"


The answer:

You end up with seemingly arbitrary limitations like single line lambdas, just like Python.


False. The I-expression:

  lambda (x y)
    display x
    + x y
corresponds to the S-expression:

  (lambda (x y)
    (display x)
    (+ x y))


The stacked C code requires much less jumping around with your eyes and it's easier to quickly get an overall idea of what the code is doing. The other version just looks too scattered, which is fine if you're working on a particular line, but most of the time I'm just glancing at major portions of code.


That was my reaction - admittedly, I'm a Python fan, myself.

The real problem is two-space indents. The example would have been much easier with even four-space indents.


It seems to me like the second example's readability stems almost entirely from the indent increase. I don't see how the stacking helps or doesn't help at all. I just saw the addition linebreaks as noise that ruins the flow of the code.

Even with the current example, I immediately said, "Inside the let." The only uncertainty for a half-second in my head was with the with-open, but that's only because 2-space tabs are pretty tight and long functions like that make reading the drop harder.


Indeed. And if your eyes aren't up to detecting that (this function is indeed long), EMACS has traditionally had two ways of manually finding that out: previous line retains the current column number, so you can position yourself at the println's opening paren and then go up. Or automatic highlighting (blinking in the monochrome days) when the point is over the closing paren.

In my personal experience (e.g. reading Lisp Machine system code) it hasn't often been a major problem. Then again I think I'm really good at following aligned columns and I can see other people not being good at this.


The Clojure example he gives of a closing paren stack that looks like ]))))])))) highlights the downside of the Clojure sugar for vectors. It's a lot easier to just keep pounding out parens at the end of an expression until they match up than it is to carefully alternate ] and ). This outweighs whatever gains were made by the vector literals IMO.

I think I would have preferred some standard reader macros instead for collection literals.


I must admit that I didn't start using paredit-mode until I began hacking in Clojure, and find it useful for just this reason. I have since 'seen the light', of course (and now have little idea how I managed to get by without it), but in CL it was far easier to, like you say, "pound the parens". So that's what I did.


It would be easy to add a nice electric-right-paren function to clojure-mode that would be bound to ')'. It would always insert the right kind of a closing paren.

Actually, I'm wondering why that doesn't already exist there. (Just checked.)


paredit-mode (http://www.emacswiki.org/emacs/ParEdit) already provides a lot of functions for manipulating s-expressions, including keeping track of your braces.


But the thing is, you shouldn't be "pounding out parens", you should be matching each closing paren with its opening one. Which the usual indentation and other formatting conventions make easy.

Or, at least that's how I do it and I typically find a variety of mistakes while doing that explicit matching.

(In case I'm not clear, as I type each closing paren I move my eyes' focus left and up matching it with the corresponding opening one.)


On the contrary, for me, having the occasional } or ] in there lets me more easily visually match up blocks of code when using a non-smart text editor. They are like little beacons of light in the darkness :-)


Poor Python programmers...they can't trail their delimiters, because they don't have any!


I did find it vaguely easier on the second one but (aside from already knowing) I'd chalk it up to two reasons:

1) 4 space indents instead of 2 making it more visually obvious.

2) I wasn't told "pop quiz" and so I didn't assume it was some sort of trick and so I trust the the indentation.


> Now try the question again, in what function or macro is the highlighted line located in? Was it easier to answer this time?

No? In either case, if it were me I'd highlight the previous close-paren (whether it's on its own line or not) to see what form it associates with. I'd do the same thing with a close-brace in C++ or an end token in Ruby. Long code blocks are a problem of every language, not just Lisp.


First, if you're writing code in an editor, there's no problem with using that editor help you write the code. Complaining about having to use an editor to use paren-matching is like complaining about screws because they don't work so great without a screwdriver. You have tools, you can use them, you don't get man-points for scratching your code into a stone slab with a chisel.

That rant aside...

Trailing parens (and trailing braces in brace-y languages) do increase local readability, but they do so at a global readibility cost. They eat lines of code, which makes it difficult to see an entire code fragment at a time. Like all trade-offs, there are reasons why you should and shouldn't, and situations where you should and shouldn't. And the important thing is, the cost of trailing parens is higher in a Lisp than it is in your average brace-delimited language. This doesn't mean that it's always the right decision in C or the wrong one in Lisp, but on average C is more likely to get a net benefit than Lisp.

Lisp simply nests more than C-style languages. This means that you pay the line-costs more often and your code density goes down a lot faster. There are a couple of reasons that Lisp nests deeper, some of them from the language itself, some cultural. From the language side, C only uses braces for control structures, and those don't return values. In Lisp, both functions and control structures use parentheses, and control structures return values so they often get used as arguments to functions, increasing the nest depth. From the cultural side, C-ish languages use variable assignment and sequencing of operations as its fundamental operations and braced control structures are less frequent, while Lisp passes values as arguments to functions as its fundamental operation. Lisp tends to use additional parentheses in places where a brace language would use a semicolon instead of a brace.[1]

I have to say, the trailing-parens didn't really help me read the example that much. Having extra parentheses didn't help me figure out what line indented with what. In fact, it made it harder because there were more lines in the way. I suppose people who favor brace-y languages count braces rather than matching indentation, but like I said before, Lisp nests more so that's a lot of counting. Again, this doesn't mean that stacking parens is always right in Lisp, or even that it's right in this code sample, since I'm not the one writing or reading it. It means that, once again, these religious wars are silly and you should pick what works best, and my $/50 is that trailing is more expensive in Lisp so it doesn't increase readability as often. FWIW, people who claim Lisp code is naturally more dense could argue that Lisp can more easily pay those extra lines.

[1] Example idiomatic C-ish language (JavaScript perhaps):

  a = foo(x, y);
  b = bar(x, y);
  return qux(a, b);
vs idiomatic equivalent lisp, which prefers to nest deeper rather than assign intermediate values:

  (qux
    (foo x y)
    (bar x y))


About your editor rant, I am not sure I agree with you. A great editor will help you for sure in productivity. However, a language shouldn't be unreadable without a correct editor.

Imagine that, to read English, you actually needed a powerful editor.. Yes, some tools help you to be more productive (such as searching, replacing, etc.).

So, again in my opinion, a good test to see if a language is readable is to print it and read it without any special tools such as syntax highlighting. If you can actually do that, imagine how syntax highlighting will make you even more productive!

By the way, it may sound absurd, but try reading English or Python without colors..


The editor helps you write Lisp, by keeping count of parens, but once it's written, it should be readable without any special support.

And reading Python without colours isn't much fun.

Haskell can get away without colours in Papers, but the authors tend to substitute lots of mathematical symbols there with the help of TeX. (And they use other typographical hints instead of colour.)


I think it's interesting that in the language features vs editor features debate, Lispers tend to be on the "language features" side, while Java programmers usually favor powerful IDEs/editors.

In this particular discussion, the sides are oddly flipped!


I've always been wondering if people who can't stand using parenthesis highlighting are also opposed to using an IDE when writing Java.


You know, I kinda like that Lisp-style C++ formatting...


Kinda Pythonic, as someone else already noted.


The real reason that parenthesis-stacking is the norm is twofold:

a) Trailing parentheses just look ugly. I can't really explain why - perhaps it's merely a cultural thing, come about only because lispers are so used to looking at stacked-parenthesis code.

b) Lisp code tends to get very nested. In C or Java code, you rarely get more than 3 trailing braces in a row. In Lisp, if you trail parentheses, you regularly end up with 5 or more lines of nothing but trailing parentheses. This contributes to the ugly factor and makes for a lot of "wasted" whitespace. It's also one of the reasons 2-space indentation is the norm for lisp instead of 4-space.

Honestly, I don't think trailing parentheses makes lisp code particularly easier to read. I agree with noahlt that it's mostly the 2-space versus 4-space indentation issue.


I think the nesting in lisp code is ultimately what makes lisp difficult. Deep nesting is hard to read!

Haskell is also deeply nested, but the where statement makes it very easy to read. A where macro would be easy to write in lisp, the real trick would be making it part of the standard lisp style.


The main advantage of parenthesis stacking is that it signals that the form you're using might be too nested. Granted, a lot of leading white space does this too. YMMV.


I would say the real solution, the one which increases readability the most, is to remove the need for many parentheses at all by using optional significant whitespace – see http://www.dwheeler.com/readable/ for an example of such a syntax. (Of course, if you don't export your code to normal, over-parenthesized Lisp before giving it to others, they will have trouble running it.)


Readability is more about consistency than some style war. If you read more code that looks like X, then code that looks like Y will look "weird" and "unreadable". The exact opposite holds for those who read more Y.

Try reading a Japanese manga translated into English. You start from the back of the book and read the right page first. Or try learning a language with "verb at end" kind of structure if English is your native tongue.

With lisp, the parentheses help create uniform parseable structure for both code and data, so I appreciate the compromise of a repeatable indentation scheme for code (using an editor) that lets you totally ignore the parens when reading.

With lisp, I do find syntax formatting such as bold keywords very effective even in the absence of color. So yeah, it is easy to create readable lisp in print as well. In fact, with bold keywords and gray parens, lisp code looks gorgeous and eminently readable.

http://xkcd.com/297/


Oh hey everyone look! Another childish post about syntax! Let's all argue about nothing!


I found both of the C code examples equally readable, but personally use neither one when I code in C. I don't (usually) stack closing braces in C mostly so that I don't accidentally kill a line with a closing brace on it. That sort of thing can theoretically happen in Lisp, but is usually easier to spot and easier to fix.

I do, however, in C put the opening brace on the same line as the definition, generally to make grep more productive. I've found it nice to be able to grep for { (or even "){" and get some useful information.


Well, if this guy is going trailing parens, I'm going to start, too. I thought someone old beardy lisp guy would jump out and shoot me if I did, but he seems to be doing fine.


You might as well try to use http://edward.oconnor.cx/elisp/hl-sexp.el everywhere, including REPL first.


This is a nice emacs addition and helps the developer, but it doesn't help the people who then look at his code using other editors, or via public repos on the web.


I write my LISP the way I write my JavaScript: I use one space for indentation, I break up my lines a lot, I nest deeply, and I tend to have a line break after the open and before the close, if the nesting is more than a layer or two deep and two broad. I'd prefer to use a tab for indentation, but I often find myself typing my code into an "eval box" (a textarea on a web page tied to a button that calls eval on the contents of the box).


It sounds like you're not using a javascript debugger (or rather rolling your own).

If that's the case I'd encourage you to familiarize yourself with some, firebug/the Chrome Inspector/IE + VS/whatever


A bit out of topic, but does anyone here write (serious) scheme/lisp code with vim ? Just a curious question ...


http://paul.graham.usesthis.com/

I use vim for so many other things that I find it tricky to switch unless I'm going to be writing lisp for the rest of the day.

I do miss paredit mode...


Most programmers prefer to use spaces instead of tabs because of some rant written ages ago by a Lisp programmer.

Spaces are better only for Lisp. Tabs are better for C++.

At least for parenthesis the custom is different.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: