Perché la comunità Lisp preferisce accumulare tutte le parentesi alla fine della funzione?


26

Perché la comunità Lisp preferisce accumulare tutte le parentesi alla fine della funzione:

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

Perché non utilizzare una convenzione come C o Java?
Bene ok, Lisp è molto più vecchio di quelle lingue, ma sto parlando dei Lispers contemporanei.

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

Nota: lo snippet di codice è tratto dal libro "The Joy of Clojure".


13
Mi è stato detto che ai tempi delle schede perforate, era pratica comune terminare un programma Lisp con una carta aggiuntiva con 80 parentesi giuste su di essa, solo per assicurarsi che ce ne fossero abbastanza per abbinare tutte le parentesi aperte nel programma .
Alger,

2
Wow mi piace. Ho sempre odiato la parentesi, ma il tuo secondo esempio mi sembra piacevole. Non bello come Python, ma miglioramento.
Eric Wilson,

5
perché possono?
Muad'Dib,

Sarebbe bello se un dialetto di Clojure promuovesse il rientro come struttura di codice. Ciò eliminerebbe del tutto la parentesi (come F #, ecc.). Naturalmente, la parentesi sarebbe comunque legale, se lo si desidera.
intrepidis,

Risposte:


28

Uno dei motivi per cui le lingue basate su Algol incoraggiano le parentesi graffe sulla propria linea è quello di incoraggiare l'aggiunta di più linee tra le parentesi delimitanti senza dover spostare le parentesi graffe. Cioè, se uno inizia con

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

è facile venire e aggiungere un'altra dichiarazione tra parentesi graffe:

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

Era stata la forma originale

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

allora dovremmo aver "spostato" due parentesi graffe, ma il mio esempio è più interessato a quest'ultimo. Qui, le parentesi graffe stanno delimitando ciò che si intende essere una sequenza di affermazioni , per lo più invocate per effetti collaterali.

Al contrario, a Lisp mancano le dichiarazioni; ogni forma è espressione , che produce un valore - anche se in alcuni rari casi (pensando al Common Lisp), quel valore viene deliberatamente scelto come "nessun valore" tramite una (values)forma vuota . È meno comune trovare sequenze di espressioni , al contrario delle espressioni nidificate . Il desiderio di "aprire una sequenza di passaggi fino al delimitatore di chiusura" non sorge così spesso, poiché man mano che le istruzioni scompaiono e i valori di ritorno diventano valuta più comune, è più raro ignorare il valore di ritorno di un'espressione, e quindi di più raro valutare una sequenza di espressioni per il solo effetto collaterale.

In Common Lisp, il prognmodulo è un'eccezione (così come i suoi fratelli):

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

Qui, prognvaluta le tre espressioni in ordine, ma scarta i valori di ritorno delle prime due. Potresti immaginare di scrivere l'ultima parentesi di chiusura sulla sua stessa linea, ma nota ancora che poiché l'ultima forma è speciale qui (non nel senso comune di Lisp di essere speciale , però), con un trattamento distinto, è più probabile che si aggiungano nuovi espressioni nel mezzo della sequenza, piuttosto che semplicemente "aggiungerne un'altra alla fine", poiché i chiamanti verrebbero quindi influenzati non solo da eventuali nuovi effetti collaterali, ma piuttosto da una probabile modifica del valore di ritorno.

Facendo una semplificazione grossolana, le parentesi nella maggior parte delle parti di un programma Lisp stanno delimitando gli argomenti passati alle funzioni, proprio come nei linguaggi simili a C, e non delimitando i blocchi di istruzioni. Per gli stessi motivi tendiamo a mantenere le parentesi che delimitano una chiamata di funzione in C attorno agli argomenti, così facciamo anche lo stesso in Lisp, con meno motivazione a deviare da quel raggruppamento stretto.

La chiusura delle parentesi ha un'importanza molto inferiore rispetto al rientro del modulo in cui si aprono. Col tempo, si impara a ignorare le parentesi e a scrivere e leggere per forma, proprio come fanno i programmatori Python. Tuttavia, non lasciare che questa analogia ti induca a pensare che rimuovere completamente le parentesi sarebbe utile. No, è un dibattito per cui è meglio salvare comp.lang.lisp.


2
penso che aggiungere alla fine non sia così raro, ad esempio (let ((var1 expr1) more-bindings-to-be-added) ...), oppure(list element1 more-elements-to-be-added)
Alexey,

14

Perché non aiuta. Usiamo il rientro per mostrare la struttura del codice. Se vogliamo separare blocchi di codice, usiamo linee veramente vuote.

Poiché la sintassi di Lisp è così coerente, le parentesi sono la guida definitiva al rientro sia per il programmatore che per l'editor.

(Per me, la domanda è piuttosto perché i programmatori C e Java amano gettare le parentesi graffe.)

Giusto per dimostrare, supponendo che questi operatori fossero disponibili in un linguaggio di tipo C:

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

Due parentesi graffe di chiusura chiudono due livelli di annidamento. La sintassi è ovviamente corretta. In analogia alla sintassi di Python, le parentesi graffe sono solo token INDENT e DEDENT espliciti.

Certo, questa potrebbe non essere la "unica vera coppia" TM , ma credo che sia solo un incidente e un'abitudine storica.


2
Inoltre, quasi tutti i redattori Lisp segnano la parentesi corrispondente quando si chiude (alcuni anche corretto )per ]o viceversa), in modo da sapere hai chiuso la giusta quantità, anche se non si controlla i livelli di rientro.
configuratore

6
SCRIVI "la domanda", perché "gettando" i token di chiusura nello stile del secondo esempio ti consente di allinearli facilmente con gli occhi e vedere cosa chiude cosa, anche se sei solo in un editor di testo senza corrispondenza automatica / evidenziando le caratteristiche.
Mason Wheeler,

1
@Mason Wheeler Sì, esattamente.
Chirone,

3
TBH, ciò che mi dice è che le parentesi in LISP sono per lo più ridondanti, con la possibilità (come con C ++) che l'indentazione possa fuorviare - se l'indentazione ti dice cosa devi sapere dovrebbe dire anche al compilatore la stessa cosa, come in Haskell e Python. A proposito: avere le parentesi graffe allo stesso livello di indentazione di if / switch / while / qualunque cosa in C ++ aiuta a prevenire casi in cui la rientranza è fuorviante: una scansione visiva verso il basso di LHS ti dice che ogni parentesi aperta è abbinata a una parentesi chiusa e quella rientranza è coerente con quelle parentesi graffe.
Steve314

1
@ Steve314: No, il rientro è ridondante. Il rientro può fuorviare anche in Python e Haskell (pensate al rientro una tantum o ai tabulari), è solo che anche il parser è fuorviante. Penso che le parentesi siano uno strumento per lo scrittore e il parser, mentre il rientro è uno strumento per lo scrittore e il lettore (umano). In altre parole, il rientro è un commento, le parentesi sono sintassi.
Svante

11

Il codice è quindi molto più compatto. Il movimento nell'editor è comunque di s-espressioni, quindi non è necessario quello spazio per l'editing. Il codice viene letto principalmente dalla struttura e dalle frasi, non dai seguenti delimitatori.


Che onore ricevere una risposta da te :) Grazie.
Chirone,

Quale editor si muove con le espressioni s? Più specificamente, come posso vimfarlo?
hasen

2
@HasenJ Potrebbe essere necessario il vimacs.vim plug-in . : P
Marco C

5

Lispers, si sa, odiosamente bletcherous come può essere, c'è un certo non so che per il secondo esempio:

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

All'inizio non riuscivo a mettere un dito sulla fonte del fascino, per così dire, e poi ho capito che mancava solo un grilletto:

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

Ecco!

Ora eserciterò la mia presa sul gangster Lisp!


2
Questa è una delle risposte più divertenti e sottovalutate che ho visto su questo sito. Punta il cappello.
byxor,

0

Nel mio caso trovo che le linee dedicate ai delimitatori siano uno spreco di spazio sullo schermo e quando scrivi il codice in C c'è anche lo stile di

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

Perché le persone inseriscono la parentesi graffa di apertura nella stessa riga del "if" per risparmiare spazio e perché sembra ridondante avere una propria linea quando il "if" ha già una propria linea.

Si ottiene lo stesso risultato mettendo insieme la parentesi alla fine. In C sembrerebbe strano perché le istruzioni finiscono in punto e virgola e la coppia di parentesi non si apre in questo modo

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

è come quel gancio di chiusura alla fine, sembra fuori posto. si apre così

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

dandoti una visione chiara del blocco e dei suoi limiti di '{' & '}'

E con l'aiuto di un editor che corrisponde alla parentesi evidenziandoli come fa Vim, puoi andare fino alla fine in cui sono racchiuse tutte le parentesi e spostarti facilmente tra loro e abbinare tutte le parentesi aperte e vedere il modulo lisp nidificato

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

puoi posizionare il cursore nella parentesi di chiusura al centro e far evidenziare la parentesi di apertura dell'if if.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.