Reading Lisp Code

[From LotsofIrritatingSillyParentheses]

I'll say it again: none of this helps the poor soul who has to read LISP code on paper.

Indentation helps. Reading obfuscated code (no matter what language) is not fun.

But some languages make it easy to obfuscate code. In fact, in some languages it's so easy to obfuscate code that it's hard to write non-obfuscated code.

LISP encourages deep nesting, which has a high obfuscation potential. Consider a procedural program with an a conditional inside another conditional inside a loop, itself inside a method definition which is inside an inner class. Already nested too deep for easy comprehension by mere mortals, but this is only four levels, and it is hard to imagine a non-trivial LISP function that would require any less. -- AnonymousDonor

Whatever do you mean? Lisp exposes some sorts of nesting that aren't so visible in other languages (at least until one reaches enlightenment and the parentheses become invisible), but what's confusing isn't the number of visible nesting levels but the complexity of what's actually going on. There's no more reason in Lisp than in any other language to write "a procedural program with a conditional inside another conditional inside a loop inside a method definition inside an inner class". Which, by the way, is at least seven levels (an inner class, by definition, must be inside something else), not four. - If the mere depth of visibly nested structures makes for confusion, then I dread to think how incomprehensible HTML must be to you. HTML -> BODY -> TABLE -> TR -> TD -> EM -> text is already seven levels, and much deeper nesting is commonplace. -- GarethMcCaughan

Is there something preventing you from factoring out the inner conditionals and loops into their own function? I've seen some truly horrid Lisp code too, but most of the production quality stuff (CLOCC, CormanLisp?) doesn't go beyond 3 levels of nesting. MAPCAR and COND can be your friends. -- JonathanTang

I've occasionally heard the rule that in Lisp programming, if there is a section of code within a given context which is more than three lines long, or more than three nestings deep, or appears in more than three places, then you should consider factoring it into a separate function. Would this be an example of ThingsInThrees?'


[From EssExpressions]

The syntax of many LispLanguage-descended ProgrammingLanguages is based on EssExpressions. Since the syntax is very regular, a paren-aware editor can automate many tasks that arise when you are EditingLispCode, like indentation.

However, no editor can ameliorate the pain of trying to read deeply nested expressions on the printed page.

Not that the readability of code on paper is important to that many people. Code is meant to be read on a screen!

I don't understand. Could you elaborate? (In practice, you don't read Lisp code by matching parens in your head. One of the tasks a parenthesis-aware editor can automate is indentation of your code, and for correctly-indented code the parentheses become virtually invisible.)


I think you'll find a lot of lispers wishing other code had nice readable sexps. Without them, it's like a person who keeps on explaining all of their ideas in excruciating detail, without the ability to build "concepts." Like when people keep repeating this idiom:

 for (i=0; i<list.size(); i++) {
     //do stuff with each list.elementAt(i)
 }
instead of building this:

 dolist (elem in list) {
     //do stuff with each elem
 }
Eventually, a central language designer might give people dolist, like some Prometheus, but people could've built it themselves. The reason this doesn't fall into anarchy is that people naturally standardize and dolist becomes part of the language. Further, people who need to build domain-specific constructs that aren't needed by 80% of programmers, will be left out in the cold. Or people with really new ideas. It's no coincidence you could build OO on top of Common Lisp, which in moderation is a great force for simplifying code.

Note, none of that requires EssExpressions, adding new control structures requires anonymous functions, and macro's to make em pretty, or in other languages macro's not necessary

  dolist(list, function(each){//do stuff to each}) is essentially the same thing, but not as pretty.

list do: [ :each | //do stuff to each] //smalltalk style of adding same new control structure
Point being it's the anonymous function that allows extending the language, and the macro's/[] syntax that makes it pretty.

Personally, I'd like a mix between C and Smalltalk and Lisp, (use Ruby) I'd love to be able to write code such as
  aList.Inject(0,[each | each.DoSomething()]);
I want those blocks, such a sweet syntax for lambda's, which is needed, yet I like Java's/CSharp's use of . to denote message, and I like (), to enclose arguments and I prefer comma's to separate a list of arguments, as natural language would, rather than lisp's spaces.
  retry(5){  <-- I want macro's so I can create new language elements with the [] reformatted as {} and placed outside the ()'s.
    //some code like uploading a file
  }
Maybe it's me, but if(true){body} seems clearer than (if true (body)), mainly because I like the distinction between arguments and body of statement. But I want those macro's so I can use any syntax I like. A language should allow you to mold it to your own brain. Interestingly, I could probably do all of this now with common lisp, hmmm..... In truth, the more I look at lisp code, the more it grows on me, I'm starting to understand the parens visually, and I certainly understand how they make macro's easier to write. Even starting to like prefix notation, and that's truly scary.

 (loop for i in list
       when (evenp i) sum i) ;add all even numbers in the list

I hope this doesn't turn into a debate on how much programmers can be trusted in writing code. There are tools to manage this complexity. If programming languages are all about stripping expressive power and creativity away from programmers because they can't be trusted... then we deserve the languages we get. -- TayssirJohnGabbour

I'm confused why people think that the only thing you need for new control structures is anonymous functions. It isn't strictly true. Look at some of the neat things Perl or Lisp does, which you can't really do easily within the framework of most languages with anonymous functions. For instance, check out Lisp's "AND" operator in this pathological example:

   (and 1 2 3 nil (quit))

The program doesn't quit. You can control the evaluation of parameters. You can fake this with anonymous functions, but it's a hassle, and it makes your programs awkward. Also, Anaphoric constructs a la Perl are rather difficult to construct since most languages employ lexical scope (and rightly so, it's a GoodThing). For example:

   (anaphoric-if (figure-out *god*) 
       (format t "Figured out god is: ~a" it)
      (format t "God remains a mystery."))
In this case, "it" is what the anaphoric-if evaluates as its expression. Please do that thread-safely without macros. Not as easy. You can do some basic language extension just with anonymous functions, but more complex and advanced extensions require macros.

Well don't be confused, I certainly didn't mean to imply they were the only way to add control structures. You'll need macro's to do fancy things like introduce new variables bindings like 'it' for anaphoric stuff, I'm well aware of this (Read OnLisp, Paul is cool), what I meant was that anonymous functions were one way to allow extending the language... I was stating that it wasn't the EssExpression that did it but the anonymous function in many cases, see smalltalk for example, all its controls structures are formed with anonymous functions aka blocks. But it's the macro that is the real powerhouse and let's you go beyond simple control structures and do crazy stuff ;).


I have found that, for the most part, the biggest difference between Lisp & Algol-ish language is punctuation.

print(string-append(get-fname(), " ", get-lname())); --> (print (string-append (get-fname) " " (get-lname)))

& even that is a pretty minor difference. The other differences are even more minor.

(Aside: The commonly cited infix v. prefix is an illusion since Algol-ish languages only use infix for arithmetic, boolean expressions, & assignment. They use prefix notation for everything else. & infix notation for special cases is easily added to Lisp.)

I've found it better to take a bit of time to get used to the punctuation & get on with learning & using Lisp rather than to flounder in concerns over syntax.


My Lisp code tends to be nested more than my code in other languages. I don't see this as much of a drawback when reading code as when editing. When I need to lift a common subexpression in Lisp, I end up doing a lot more editing than in other languages, because lifting is effectively unnesting part of the code. Perhaps a better editor (or better knowledge of how to use my editor) is the answer. It certainly seems as if lifting common subexpressions could be automated.

On the reading side, I find nesting obscures tail-calls.


Also see EssExpressions, EditingLispCode.


CategoryLisp


EditText of this page (last edited April 10, 2009) or FindPage with title or text search