Dlaczego społeczność Lisp woli gromadzić wszystkie nawiasy na końcu funkcji?

26

Dlaczego społeczność Lisp woli gromadzić wszystkie nawiasy na końcu funkcji:

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

Dlaczego nie zastosować konwencji takiej jak C lub Java?
No cóż, Lisp jest znacznie starszy od tych języków, ale mówię o współczesnych Lispersach.

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

Uwaga: Fragment kodu pochodzi z książki „Radość Clojure”.

Chiron
źródło
13
Powiedziano mi, że w czasach kart perforowanych powszechną praktyką było kończenie programu Lisp z dodatkową kartą z 80 prawymi nawiasami, aby upewnić się, że jest wystarczająca liczba pasujących do wszystkich otwartych nawiasów w programie .
Alger,
2
Wow, podoba mi się. Zawsze nienawidziłem nawiasu, ale twój drugi przykład wydaje mi się przyjemny. Nie tak miły jak Python, ale ulepszenie.
Eric Wilson,
5
bo mogą?
Muad'Dib,
Byłoby miło, gdyby dialekt Clojure promował wcięcie w strukturę kodu. To by całkowicie wyeliminowało nawiasy (np. F # itp.). Oczywiście, w razie potrzeby, nawiasy byłyby nadal legalne.
intrepidis

Odpowiedzi:

28

Jednym z powodów, dla których języki oparte na Algolu zachęcają nawiasy klamrowe we własnej linii, jest zachęcanie do dodawania kolejnych linii między nawiasami klamrowymi bez konieczności przesuwania nawiasów klamrowych. To znaczy, jeśli się zaczyna

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

łatwo jest dołączyć i dodać kolejne oświadczenie w nawiasach klamrowych:

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

Gdyby pierwotna forma była

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

wtedy musielibyśmy „przesunąć” dwa nawiasy klamrowe, ale mój przykład dotyczy bardziej tego drugiego. Tutaj nawiasy klamrowe określają, co ma być sekwencją instrukcji , najczęściej wywoływanych ze względu na efekt uboczny.

I odwrotnie, Lisp nie ma instrukcji; każda forma jest wyrażeniem , dającym pewną wartość - nawet jeśli w niektórych rzadkich przypadkach (myśląc o Common Lisp), wartość ta jest celowo wybierana jako „bez wartości” za pomocą pustej (values)formy. Rzadziej jest znaleźć sekwencje wyrażeń , niż wyrażenia zagnieżdżone . Pragnienie „otwarcia sekwencji kroków aż do zamykającego ogranicznika” nie pojawia się tak często, ponieważ gdy instrukcje znikają, a zwracane wartości stają się bardziej powszechną walutą, rzadziej ignorowane jest zwracane wartości wyrażenia, a tym samym więcej rzadko ocenia się sekwencję wyrażeń pod kątem samego efektu ubocznego.

W Common Lisp prognforma jest wyjątkiem (podobnie jak jej rodzeństwo):

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

Tutaj prognocenia trzy wyrażenia w kolejności, ale odrzuca zwracane wartości pierwszych dwóch. Można sobie wyobrazić pisanie tego ostatniego nawiasu zamykającego na jego własnej linii, ale zauważmy jeszcze raz, że ponieważ ostatnia forma jest tutaj wyjątkowa ( choć nie w sensie bycia wyjątkowym ), z wyraźnym traktowaniem, jest bardziej prawdopodobne, że ktoś doda nowy wyrażenia w środku sekwencji, a nie tylko „dodawanie kolejnego na końcu”, ponieważ na osoby dzwoniące miałyby wpływ nie tylko nowe skutki uboczne, ale raczej prawdopodobna zmiana wartości zwracanej.

Znacząco upraszczając, nawiasy w większości części programu Lisp ograniczają argumenty przekazywane do funkcji - tak jak w językach podobnych do C - i nie ograniczają bloków instrukcji. Z tych samych powodów mamy tendencję do trzymania nawiasów ograniczających wywołanie funkcji w C blisko argumentów, podobnie robimy to samo w Lisp, z mniejszą motywacją do odchodzenia od tego ścisłego grupowania.

Zamykanie nawiasów ma znacznie mniejsze znaczenie niż wcięcie formy, w której się otwierają. Z czasem uczy się ignorować nawiasy i pisać i czytać według kształtu - podobnie jak robią to programiści Python. Nie pozwól jednak, aby ta analogia doprowadziła cię do wniosku, że całkowite usunięcie nawiasów byłoby opłacalne. Nie, na tę debatę najlepiej zapisać comp.lang.lisp.

seh
źródło
2
myślę, że dodawanie na końcu nie jest tak rzadkie, np. (let ((var1 expr1) more-bindings-to-be-added) ...)lub(list element1 more-elements-to-be-added)
Alexey
14

Ponieważ to nie pomaga. Używamy wcięć, aby pokazać strukturę kodu. Jeśli chcemy oddzielić bloki kodu, używamy naprawdę pustych linii.

Ponieważ składnia Lisp jest tak spójna, nawiasy są ostatecznym przewodnikiem po wcięciach zarówno dla programisty, jak i edytora.

(Dla mnie pytanie brzmi raczej, dlaczego programiści C i Java lubią ominąć aparaty ortodontyczne).

Aby to wykazać, zakładając, że operatory te były dostępne w języku podobnym do C:

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

Dwa zamykające nawiasy zamykają dwa poziomy zagnieżdżenia. Składnia jest oczywiście poprawna. Analogicznie do składni Pythona, nawiasy klamrowe są po prostu jawnymi tokenami INDENT i DEDENT.

Oczywiście, to może nie być „jeden prawdziwy styl klamra” TM , ale wierzę, że to tylko przypadek historyczny i nawykiem.

Svante
źródło
2
Ponadto, prawie wszyscy redaktorzy LISP zaznaczyć pasujący nawias przy zamykaniu (niektóre również prawidłowe )do ]lub odwrotnie), więc wiesz, że zamknięte odpowiednią ilość nawet jeśli nie sprawdzić poziomy wcięć.
konfigurator
6
WRT „pytanie”, ponieważ „rzucanie dookoła” zamykających tokenów w stylu drugiego przykładu pozwala łatwo ustawić je w linii oczami i zobaczyć, co się zamyka, nawet jeśli jesteś tylko w edytorze tekstu bez automatycznego dopasowywania / wyróżnianie funkcji.
Mason Wheeler,
1
@Mason Wheeler Tak, dokładnie.
Chiron,
3
TBH, to co mówi mi to, że pareny w LISP są w większości zbędne, z możliwością (jak w C ++), że wcięcie może wprowadzić w błąd - jeśli wcięcie mówi ci, co musisz wiedzieć, to powinno powiedzieć kompilatorowi to samo, jak w Haskell i Python. BTW - mając nawiasy klamrowe na tym samym poziomie wcięcia co if / switch / while / cokolwiek w C ++ pomaga zapobiegać przypadkom, w których wcięcie jest mylące - skan wizualny w dół LHS informuje, że każda nawias klamrowy jest dopasowany do nawiasu klamrowego i to wcięcie jest zgodne z tymi nawiasami klamrowymi.
Steve314,
1
@ Steve314: Nie, wcięcie jest zbędne. Wcięcia mogą również wprowadzać w błąd w Pythonie i Haskell (pomyśl o jednorazowych wcięciach lub tabelach), po prostu parser również wprowadza w błąd. Myślę, że nawiasy są narzędziem pisarza i analizatora składni, podczas gdy wcięcia są narzędziem pisarza i (ludzkiego) czytelnika. Innymi słowy, wcięcie jest komentarzem, nawiasy są składnią.
Svante
11

Kod jest wtedy znacznie bardziej zwarty. W każdym razie ruch w edytorze odbywa się za pomocą wyrażeń s, więc nie potrzebujesz tego miejsca do edycji. Kod jest odczytywany głównie przez strukturę i zdania - nie przez następujące ograniczniki.

Rainer Joswig
źródło
Co za zaszczyt otrzymać odpowiedź od ciebie :) Dzięki.
Chiron,
Jaki edytor porusza się według wyrażeń s? Mówiąc dokładniej, jak mogę vimto zrobić?
hasen
2
@HasenJ Możesz potrzebować vimacs.vim wtyczki . : P
Mark C
5

Wiesz, Lispers, jakkolwiek nienawistnie sflaczały, w drugim przykładzie jest pewna je ne sais quoi :

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

Na początku nie mogłem położyć palca na źródle czaru, że tak powiem, a potem zdałem sobie sprawę, że po prostu brakuje spustu:

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

Voilà!

Będę teraz ćwiczyć mój gangsterski chwyt Lisp!

Kaz
źródło
2
To jedna z najśmieszniejszych i najbardziej niedocenianych odpowiedzi, jakie widziałem na tej stronie. Przechylam kapelusz.
byxor
0

W moim przypadku uważam, że wiersze dedykowane ogranicznikom to marnowanie miejsca na ekranie, a kiedy piszesz kod w C, istnieje również styl

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

Dlaczego ludzie umieszczają otwierający nawias klamrowy w tej samej linii „if”, aby zaoszczędzić miejsce i ponieważ wygląda na zbędne, aby mieć własną linię, gdy „if” ma już własną linię.

Ten sam wynik osiągasz, łącząc nawias na końcu. W C wyglądałoby dziwnie, ponieważ instrukcje kończą się średnikiem, a para nawiasów nie otwiera się w ten sposób

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

to jest jak ta klamra zamykająca na końcu, wygląda nie na miejscu. tak się otwiera

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

dając ci wyraźny obraz bloku i jego granic przez „{” i „}”

Za pomocą edytora, który dopasowuje nawiasy, podświetlając je tak, jak robi to vim, możesz przejść do końca, w którym wszystkie nawiasy są zapakowane, i łatwo się po nich poruszać oraz dopasowywać wszystkie otwierające pareny i zobaczyć zagnieżdżoną lisp

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

możesz umieścić kursor w zamykającym się filarze pośrodku i wyróżnić otwierający paren if-let.

kisai
źródło