开发者

Lisp Parentheses

开发者 https://www.devze.com 2022-12-19 01:25 出处:网络
Why do Lispers format their code like shown in sample 1 instead of as shown in sample 2?To me (and I guess, to most others coming from different programming backgrounds than Lisp), the formatting show

Why do Lispers format their code like shown in sample 1 instead of as shown in sample 2? To me (and I guess, to most others coming from different programming backgrounds than Lisp), the formatting shown in sample 2 would be easier to read. Is there any particular reason why Lispers prefer the sample 1 style?

Sample 1

(defun factorial (n)
  (if (<= n 1)
    1
    (* n (factorial (- n 1)))))

Sample 2

(defun factorial (n)
  (if (<= n 1)
    1
    (* n (开发者_如何转开发factorial (- n 1)))
  )
)


LISP IDE environments tend to balance parentheses automatically and manage indents based on nesting level. Sample 2 does not bring any advantages in those situations.

In the C/FORTRAN/Pascal heritage, you tend to emphasize sequencing over nesting (code parse trees are shallower and wider). End of scope is a more significant event in your code: hence emphasis has been and still to some extent is more important.


For experienced Lisp users, the nesting level is more important than finding closing parentheses. Putting closing parentheses on their own lines does not really help with nesting levels.

The basic idea is that the parentheses are directly AROUND their contents.

(a)

and not

(a
)

What follows is this:

(defun foo (bar)
  (foo (bar (baz
              ...
             )
         ...
        )
    ...
   )
 )

vs.

(defun foo (bar)
  (foo (bar (baz ...) ...) ...))

One of the basic ideas when editing Lisp text is, that you can select a list by (double-) clicking on the parentheses (or by using a key command when the cursor is inside the expression or on the parentheses). Then you can cut/copy the expression and paste it into another position in some other function. Next step is to select the other function and re-indent the function. Done. There is no need to remove or introduce new lines for for closing parentheses. Just paste and re-indent. It just fits in. Otherwise you would either waste time formatting the code, or you would need to re-format the code with some editor tool (so that closing parentheses are on their own lines. Most of the time it that would create additional work and hinders moving code around.

There is one occasion where experienced Lispers would sometime write closing parentheses on their own line:

(defvar *persons*
   (list (make-person "fred")
         (make-person "jane")
         (make-person "susan")
         ))

Here it indicates that new persons can be added. Place the cursor directly before the second closing parentheses on the last line, press c-o (open line), add the clause and indent the parentheses that they are aligned again. This saves the 'trouble' to find the right parentheses and then press return, when all parentheses are closed on one line.


Because there is no need whatsoever to line up closing parens. It doesn't add anything semantically. To know how many to close, most Lispers use a good editor, such as Emacs, that matches parens to closing parens and hence makes the task trivial.

In Python, there are no closing parens or end keywords at all, and Pythonistas live just fine.


After a while with Lisp you don't notice the parentheses any longer, so your example two comes across as having a bunch of unnecessary whitespace in the end of it. More specifically, most lispers use an editor that is aware of parentheses and takes care of closing the forms correctly, so you as the developer don't need to match opening and closing parentheses like you do in e.g. C.

As for whether the last form should be

(* n (factorial (- n 1)))

or

(* n
   (factorial (- n 1)))

that mostly comes down to personal preference and how much stuff is going on in the code (In this case I'd prefer the former just because there is so little happening in the code).


Besides the fact that most Lisp IDE's use paren matching, the amount of space that you would use to write any reasonable program would be ridiculous! You would get carpal tunnel from all the scrolling. A 1,000 line program would be close to a million lines of code if you put all the closing parenthesis on their own line. It may look prettier and be easier to read in a small program, but that idea wouldn't scale well at all.


Why do C programmers write things like:

((a && b) || (c && d))

instead of

((a && b) || (c && d
             )
)

Wouldn't #2 be easier to read? :)

Seriously, though, the bunched up closing style works well for other languages, too.

#include <stdlib.h>
#include <stdio.h>

int main(void)
{ int i, j, k;
  for (i = 0; i < 1000; i++)
  { for (j = 0; j < 1000; j++)
    { for (k = 0; k < 1000; k++)
      { if (i * i + j * j == k * k)
        { printf("%d, %d, %d\n", i, j, k); } } } }
  return 0; }

After a while, you don't "see" the braces, just the structure given by the indentation. if/else looks like this:

if (cond)
{ stmt;
  stmt; }
else
{ stmt; }

switch:

switch (expr)
{ case '3':
    stmt;
    break;
  case '4':
  { stmt;
    break; } }

structs:

struct foo
{ int x;
  struct bar
  { int y; };
  union u
  { int a, b;
    char c; }; };


Just first and obvious reason: 4 LOC in first case vs. 7 LOC in second one.

Any text editor that is aware of LISP syntax will highlight you matched/mismatched parentheses, so it's not the problem.


I experienced the same dilemma in PHP using the CakePHP framework and its many nested array() structures, sometimes 5+ layers deep. After a short while I realized that being OCD about the closing parenthesis did nothing but waste my precious development time and distracted me from the end goal. The indentation was to be the most useful aspect of the formatting.


Saving all the space seems like the primary reason. If it's almost as readable (esp. once you're used to the syntax) why use the extra 3 lines.


Because Lisp programmers look at the indentation and "shape", not the brackets.


You might consider Lisp variants which do without the (). In Genyris it looks like this:

def factorial (n)
  if (< n 2) 1
    * n
      factorial (- n 1)


Seems like this is not a real question, just a judgement on Lisp, but the answer is perfectly obvious (but not, apparently, to non-Lispers!): parentheses in Lisp are in no way equivalent to braces in C-like languages -- rather, they're precisely equivalent to parentheses in C-like languages. Lispers don't generally write

(foo bar baz
)

for exactly the same reason C programmers don't generally write

foo(bar, baz
   );

The reason

(if foo
    (bar)
    (baz))

looks different from

if (foo) {
   bar();
} else {
   baz();
}

has more to do with the if than the parentheses!


I have an explanation as to C programmers have a problem with Lispers putting all the remaining closing parentheses at the end. The practice seems to me that the meaning of parentheses could be the reason. This explanation overlaps and expands on some other answers.

To a C programmer (or other language using {braces} like C does), Lisp looks like it uses #| comments |# instead of /* comments */. And looks like Lisp parentheses () are in place of C braces {}.

But really, the Lisp parentheses mean very close to the same thing as in C (and similar languages using parentheses). Consider

defun f () nil

This defines a function f that does absolutely nothing, nil. And I type that line exactly as is into the Lisp Listener, and it just responds "F". In other words, it accepts it, without a beginning and ending parentheses around the whole thing.

What I think happens when you type that into the Listener is the Listener passes what you typed to Eval. Passing it to Eval could be represented as:

Eval( Listener() )

And Listener accepts my input and returns what I typed. And you would consider "Listener()" replaced in the expression so that it becomes

Eval (defun f () nil)

REPL (read, eval, print, loop) could be represented conceptually as a recursion

/* REPL expressed in C */
Loop()
{
    Print ( Eval ( Listen () ) );
    Loop();
}

Understood in the example is that Listen(), Eval(), and Print() are defined elsewhere or are built into the language.

The Listener lets me type

setf a 5

and also lets me type

(setf a 5)

but complains when I type

((setf a 5))

If parentheses were equivalent to C language braces, then the listener would accept it. In my C\C++ compilers, I can type

void a_fun(void)
{{
}}

without complaint. I can even type

void a_fun(void)
{{{
}}}

with no complaint from the C/C++ compiler.

But if I dare to type

void a_fun((void))
{
}

my C/C++ compiler complains--just like the Lisp listener complains!

For a C programmer writing nested function calls, seems it is more natural to write

fun_d( fun_c( fun_b( fun_a())));

than to write

fun_d( fun_c( fun_b( fun_a()
                   )
             )
      );

In C, braces demarcate a block of code. And a block of code is part of a function definition. Lisp parentheses don't demarcate blocks. They are somewhat like C's parentheses. In C, the parentheses demarcate a list; maybe a list of parameters passed to a function.

In Lisp, seems the opening parentheses "(" means we are going to start a new list, to which the CAR side of a CONS construct will point. Then we list the items to be contained or optionally pointed to by the CAR side of the CONS, with the CDR side pointing to nil (for end of list) or the next CONS. The closing parenthesis ")" means to terminate the list with a nil, and go back to continue the previous level of listing. So, C language does not do CONS's. So, there is a little bit of a difference between C's parentheses and Lisp's. Even so, seems very close, maybe even practically the same thing.

Some have written that Lisp becomes more like C if you move the function outside the parentheses. For example,

(setf a 5)

becomes

setf(a 5)

But what it really is, is that Eval requires that the first item in a list be the function that Eval needs to call. It is more like passing a C function a pointer to a function. So, what it really is is

eval(setf a 5)

And that looks more like C. You can even type it in the listener just like that without complaint from the listener or eval. You are just saying that eval should call eval which should then call setf. The rest of the list are parameters to setf.

Anyway, that's just what I think I see.

It's just that Eval needs to know which processor to call.

And I think this explanation provides an understanding of why C programmers think initially that Lispers should align closing parentheses under the opening parentheses which they close. The C programmer mistakenly thinks that Lisp's parenthesis correspond to C's braces. They don't. Lisp's parentheses correspond to C's parentheses.

0

精彩评论

暂无评论...
验证码 换一张
取 消