Warum zieht es die Lisp-Community vor, alle Klammern am Ende der Funktion zu akkumulieren?

26

Warum zieht es die Lisp-Community vor, alle Klammern am Ende der Funktion zu akkumulieren:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)))

Warum nicht eine Konvention wie C oder Java verwenden?
Nun gut, Lisp ist viel älter als diese Sprachen, aber ich spreche über die zeitgenössischen Lispers.

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

Hinweis: Das Code-Snippet stammt aus dem Buch "The Joy of Clojure".

Chiron
quelle
13
Mir wurde gesagt, dass es in den Tagen der Lochkarten üblich war, ein Lisp-Programm mit einer zusätzlichen Karte mit 80 rechten Klammern zu beenden, nur um sicherzustellen, dass es genug gab, um mit allen offenen Klammern im Programm übereinzustimmen .
Alger
2
Wow, Ich mag es. Ich habe die Klammer immer gehasst, aber Ihr zweites Beispiel sieht für mich angenehm aus. Nicht so schön wie Python, aber eine Verbesserung.
Eric Wilson
5
weil sie können?
Muad'Dib,
Es wäre schön, wenn ein Dialekt von Clojure die Einrückung zu einer Codestruktur machen würde. Das würde die Klammer komplett aufheben (wie F #, etc ..). Klammern wären natürlich immer noch zulässig, wenn dies gewünscht wird.
intrepidis

Antworten:

28

Ein Grund, warum Algol-basierte Sprachen die geschweiften Klammern in ihrer eigenen Zeile anregen, besteht darin, mehr Zeilen zwischen den Begrenzungsklammern einzufügen, ohne die geschweiften Klammern verschieben zu müssen. Das heißt, wenn man mit anfängt

if (pred)
{
  printf("yes");
}

Es ist einfach mitzukommen und eine weitere Aussage in die geschweiften Klammern einzufügen:

if (pred)
{
  printf("yes");
  ++yes_votes;
}

Wäre die ursprüngliche Form gewesen

if (pred)
{ printf("yes"); }

dann müssten wir zwei Klammern "bewegt" haben , aber mein Beispiel befasst sich mehr mit letzterer. Hier begrenzen die geschweiften Klammern, was beabsichtigt ist, eine Folge von Anweisungen zu sein , die meistens für Nebenwirkungen aufgerufen werden.

Umgekehrt fehlen Lisp Aussagen; Jede Form ist ein Ausdruck , der einen Wert ergibt - auch wenn in einigen seltenen Fällen (unter Berücksichtigung von Common Lisp) dieser Wert absichtlich als "keine Werte" über eine leere (values)Form ausgewählt wird. Es ist weniger üblich, Sequenzen von Ausdrücken zu finden , als verschachtelte Ausdrücke . Der Wunsch, "eine Abfolge von Schritten bis zum schließenden Begrenzer zu eröffnen", tritt nicht so häufig auf, da mit dem Wegfall von Anweisungen und der Verbreitung von Rückgabewerten der Rückgabewert eines Ausdrucks seltener ignoriert wird selten, um eine Sequenz von Ausdrücken allein auf Nebenwirkungen zu untersuchen.

In Common Lisp ist das prognFormular eine Ausnahme (wie auch seine Geschwister):

(progn
  (exp-ignored-return-1)
  (exp-ignored-return-2)
  (exp-taken-return))

Hier prognwerden die drei Ausdrücke nacheinander ausgewertet, die Rückgabewerte der ersten beiden werden jedoch verworfen. Man könnte sich vorstellen , dass die letzten schließenden Klammer in einer eigenen Zeile zu schreiben, aber beachten Sie wieder , dass seit der letzten Form hier speziell ist (nicht im Common Lisp Sinn des Seins besondere , obwohl), mit unterschiedlichen Behandlung, es ist wahrscheinlich , dass man möchte hinzufügen , neue Ausdrücke in der Mitte der Sequenz und nicht nur "Hinzufügen eines weiteren am Ende", da Aufrufer dann nicht nur von neuen Nebenwirkungen, sondern auch von einer wahrscheinlichen Änderung des Rückgabewerts betroffen wären.

Um eine grobe Vereinfachung zu erreichen, begrenzen die Klammern in den meisten Teilen eines Lisp-Programms Argumente, die an Funktionen übergeben werden - genau wie in C-ähnlichen Sprachen - und nicht Anweisungsblöcke. Aus den gleichen Gründen neigen wir dazu, die Klammern, die einen Funktionsaufruf in C begrenzen, in der Nähe der Argumente zu belassen, und tun dies auch in Lisp, mit weniger Motivation, von dieser engen Gruppierung abzuweichen.

Das Schließen der Klammern ist weniger wichtig als das Einrücken des Formulars, in dem sie geöffnet werden. Mit der Zeit lernt man, die Klammern zu ignorieren und nach Form zu schreiben und zu lesen - ähnlich wie es Python-Programmierer tun. Lassen Sie sich jedoch nicht von dieser Analogie dazu verleiten, zu denken, dass es sich lohnen würde , die Klammern vollständig zu entfernen . Nein, das ist eine Debatte, für die man am besten aufgehoben ist comp.lang.lisp.

seh
quelle
2
Ich denke, dass das Hinzufügen am Ende nicht so selten ist, zB (let ((var1 expr1) more-bindings-to-be-added) ...)oder(list element1 more-elements-to-be-added)
Alexey
14

Weil es nicht hilft. Wir verwenden Einrückungen, um die Codestruktur anzuzeigen. Wenn wir Codeblöcke trennen wollen, verwenden wir wirklich leere Zeilen.

Da die Lisp-Syntax so konsistent ist, sind die Klammern sowohl für den Programmierer als auch für den Editor die endgültige Richtlinie für das Einrücken.

(Für mich ist die Frage eher, warum C- und Java-Programmierer gerne ihre Klammern umdrehen.)

Nur um zu demonstrieren, unter der Annahme, dass diese Operatoren in einer C-ähnlichen Sprache verfügbar waren:

Foo defer_expensive (Thunk cheap, Thunk expensive) {
    if (Foo good_enough = force (cheap)) {
        return good_enough; }
    else {
        return force (expensive); }}

Zwei schließende Klammern schließen zwei Verschachtelungsebenen. Die Syntax ist offensichtlich korrekt. In Analogie zur Python-Syntax sind die geschweiften Klammern nur explizite INDENT- und DEDENT-Token.

Natürlich ist dies möglicherweise nicht der "One True Brace Style" TM , aber ich glaube, dass dies nur ein historischer Zufall und eine Gewohnheit ist.

Svante
quelle
2
Auch fast alle Lisp - Editoren markieren Sie die passende Klammer beim Schließen (einige auch richtig )zu ]oder umgekehrt), so dass Sie wissen , dass Sie die richtige Menge geschlossen , auch wenn Sie nicht über die Einrückungen überprüfen.
Konfigurator
6
WRT "the question", weil Sie durch "Herumwerfen" von schließenden Token im Stil des zweiten Beispiels einfach die Augen aufreihen und sehen können, was was schließt, selbst wenn Sie sich nur in einem Texteditor ohne automatische Zuordnung befinden. Hervorhebungsfunktionen.
Mason Wheeler
1
@ Mason Wheeler Ja, genau.
Chiron
3
TBH, dies sagt mir, dass die Parens in LISP größtenteils überflüssig sind, mit der Möglichkeit (wie bei C ++), dass der Einzug irreführend sein kann - wenn der Einzug Ihnen sagt, was Sie wissen müssen, sollte er dem Compiler dasselbe mitteilen. wie in Haskell und Python. Übrigens: Wenn sich die geschweiften Klammern auf der gleichen Einrückungsstufe befinden wie if / switch / while / whatever in C ++, wird verhindert, dass die Einrückung irreführend ist. Durch einen visuellen Scan in der linken Spalte werden Sie darüber informiert, dass jede offene Klammer einer geschlossenen Klammer entspricht und diese Einrückung stimmt mit diesen Klammern überein.
Steve314
1
@ Steve314: Nein, der Einzug ist überflüssig. Einrückungen können auch in Python und Haskell irreführend sein (denken Sie an einmalige Einrückungen oder Tabellen). Es ist nur so, dass der Parser ebenfalls irreführend ist. Ich denke, dass die Klammern ein Werkzeug für den Schreiber und den Parser sind, während die Einrückung ein Werkzeug für den Schreiber und den (menschlichen) Leser ist. Mit anderen Worten, Einrückung ist ein Kommentar, Klammern sind Syntax.
Svante
11

Der Code ist dann viel kompakter. Die Bewegung im Editor erfolgt ohnehin durch S-Ausdrücke, sodass Sie diesen Platz nicht zum Bearbeiten benötigen. Code wird hauptsächlich nach Struktur und Sätzen gelesen - nicht nach Trennzeichen.

Rainer Joswig
quelle
Was für eine Ehre, eine Antwort von Ihnen zu bekommen :) Danke.
Chiron
Welcher Editor bewegt sich durch S-Ausdrücke? Wie komme ich vimdazu?
Hasen
2
@HasenJ Möglicherweise benötigen Sie das vimacs.vim Plugin . : P
Mark C
5

Lispers, wissen Sie, gehässig bletcherous es auch sein mag, es ist eine gewisse je ne sais quoi das zweite Beispiel:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

Zuerst konnte ich sozusagen keinen Finger auf die Quelle der Anziehungskraft legen, und dann bemerkte ich, dass nur ein Auslöser fehlt:

(defn defer-expensive [cheap expensive]      
  (if-let [good-enough (force cheap)]
    good-enough   ;   )
    (force expensive) 
  )
)

Voilà!

Ich werde jetzt meinen Lisp-Gangstergriff üben!

Kaz
quelle
2
Dies ist eine der lustigsten und am meisten unterschätzten Antworten, die ich auf dieser Website gesehen habe. Ich tippe auf meinen Hut.
Byxor
0

In meinem Fall finde ich die Zeilen, die Begrenzern gewidmet sind, eine Verschwendung von Platz auf dem Bildschirm, und wenn Sie Code in C schreiben, gibt es auch den Stil von

if (pred) {
   printf("yes");
   ++yes_votes;
}

Warum setzen die Leute die öffnende Klammer in dieselbe Zeile des "if", um Platz zu sparen, und weil es überflüssig erscheint, eine eigene Zeile zu haben, wenn das "if" bereits eine eigene Zeile hat?

Sie erreichen das gleiche Ergebnis, indem Sie am Ende die Klammern setzen. In C würde komisch aussehen, weil Anweisungen mit einem Semikolon enden und das Klammerpaar nicht so geöffnet wird

{if (pred)
    printf("yes");
}

es ist wie die schließende Klammer am Ende, sieht fehl am Platz. es öffnet sich so

if (pred) {
    printf("yes");
}

Verschaffen Sie sich mit '{' & '}' einen klaren Überblick über den Block und seine Grenzen

Und mit Hilfe eines Editors, der Klammern anpasst, indem er sie hervorhebt, wie es vim tut, können Sie bis zum Ende gehen, wo alle Klammern gepackt sind, sie leicht durchgehen und mit allen Eröffnungsklammern übereinstimmen und das verschachtelte Lispel-Formular sehen

(defn defer-expensive [cheap expensive]
    _(if-let [good-enough (force cheap)]
    good-enough
    (force expensive)_))

Sie können den Cursor in die abschließende Parene in der Mitte setzen und die öffnende Parene der If-Let hervorheben.

Kisai
quelle