LET kontra LET * w Common Lisp


81

Rozumiem różnicę między LET i LET * (wiązanie równoległe i sekwencyjne) i jako kwestia teoretyczna ma to doskonały sens. Ale czy jest jakiś przypadek, w którym kiedykolwiek potrzebowałeś LET? W całym moim kodzie Lispa, który ostatnio przeglądałem, możesz zamienić każdy LET na LET * bez zmian.

Edycja: OK, rozumiem, dlaczego jakiś facet wymyślił LET *, prawdopodobnie jako makro, dawno temu. Moje pytanie brzmi, biorąc pod uwagę, że LET * istnieje, czy jest powód, dla którego LET ma pozostać? Czy napisałeś jakiś rzeczywisty kod Lispa, w którym LET * nie działałby tak dobrze, jak zwykły LET?

Nie kupuję argumentu wydajności. Po pierwsze, rozpoznawanie przypadków, w których LET * można skompilować w coś tak wydajnego jak LET, po prostu nie wydaje się takie trudne. Po drugie, w specyfikacji CL jest wiele rzeczy, które po prostu nie wydają się być zaprojektowane z myślą o wydajności. (Kiedy ostatnio widziałeś LOOP z deklaracjami typu? Tak trudno to rozgryźć, że nigdy ich nie widziałem.) Przed testami porównawczymi Dicka Gabriela z późnych lat osiemdziesiątych, CL było po prostu powolne.

Wygląda na to, że jest to kolejny przypadek wstecznej kompatybilności: mądrze, nikt nie chciał ryzykować złamania czegoś tak fundamentalnego jak LET. Takie było moje przeczucie, ale pocieszające jest usłyszeć, że nikt nie ma głupio prostego przypadku, którego mi brakowało, w którym LET sprawiło, że wiele rzeczy było śmiesznie łatwiejszych niż LET *.


paralela to zły dobór słów; widoczne są tylko poprzednie wiązania. równoległe wiązanie byłoby bardziej podobne do powiązań Haskella „… gdzie…”.
jrockway

Nie chciałem mylić; Myślę, że to są słowa użyte w specyfikacji. :-)
Ken

12
Równoległość jest poprawna. Oznacza to, że wiązania ożywają w tym samym czasie i nie widzą się nawzajem i nie zasłaniają się. W żadnym momencie nie istnieje środowisko widoczne dla użytkownika, które zawierałoby niektóre zmienne zdefiniowane w LET, ale nie zawierało innych.
Kaz

Haskells, w których wiązania są bardziej podobne do letrec. Mogą zobaczyć wszystkie powiązania na tym samym poziomie zakresu.
Edgar Klerks

1
Pytanie „czy jest przypadek, w którym letjest to potrzebne?” przypomina to trochę pytanie „czy jest przypadek, w którym potrzebne są funkcje z więcej niż jednym argumentem?”. leti let*nie istnieją z powodu pewnego pojęcia wydajności, istnieją, ponieważ pozwalają ludziom komunikować się z innymi ludźmi podczas programowania.
tfb

Odpowiedzi:


86

LETsam w sobie nie jest prawdziwym prymitywem w funkcjonalnym języku programowania , ponieważ można go zastąpić LAMBDA. Lubię to:

(let ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

jest podobne do

((lambda (a1 a2 ... an)
   (some-code a1 a2 ... an))
 b1 b2 ... bn)

Ale

(let* ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

jest podobne do

((lambda (a1)
    ((lambda (a2)
       ...
       ((lambda (an)
          (some-code a1 a2 ... an))
        bn))
      b2))
   b1)

Możesz sobie wyobrazić, co jest prostsze. LETa nie LET*.

LETułatwia zrozumienie kodu. Widzi się kilka powiązań i można czytać każde wiązanie indywidualnie bez potrzeby rozumienia przepływu „efektów” z góry na dół / od lewej do prawej (rebindings). Używanie LET*sygnałów do programisty (tego, który czyta kod), że powiązania nie są niezależne, ale istnieje pewien rodzaj przepływu z góry na dół - co komplikuje sprawę.

Common Lisp ma regułę, że wartości dla powiązań w programie LETsą obliczane od lewej do prawej. Po prostu, jak wartości dla wywołania funkcji są oceniane - od lewej do prawej. Jest to więc LETkoncepcyjnie prostsza instrukcja i powinna być używana domyślnie.

TypyLOOP ? Są używane dość często. Istnieje kilka prymitywnych form deklaracji typu, które są łatwe do zapamiętania. Przykład:

(LOOP FOR i FIXNUM BELOW (TRUNCATE n 2) do (something i))

Powyżej deklaruje zmienną ijako fixnum.

Richard P. Gabriel opublikował swoją książkę o benchmarkach Lispa w 1985 i wtedy te benchmarki były również używane z Lispsami innymi niż CL. Sam Common Lisp był zupełnie nowy w 1985 roku - książka CLtL1, która opisywała język, została właśnie opublikowana w 1984 roku. Nic dziwnego, że implementacje nie były wówczas zbytnio zoptymalizowane. Zaimplementowane optymalizacje były w zasadzie takie same (lub mniej), co poprzednie implementacje (jak MacLisp).

Ale dla LETvs. LET*Główną różnicą jest to, że za pomocą kodu LETjest łatwiejsze do zrozumienia dla ludzi, gdyż klauzule wiążące są niezależne od siebie - zwłaszcza, że to jest zły styl wykorzystać zmienne lewej do prawej oceny (nie ustawienie jako strony efekt).


3
Nie? Nie! Lambda nie jest prawdziwym prymitywem, ponieważ można ją zastąpić LET i lambdą niższego poziomu, która zapewnia API, aby uzyskać dostęp do wartości argumentów: (low-level-lambda 2 (let ((x (car %args%)) (y (cadr args))) ...) :)
Kaz

36

Nie potrzebujesz LET, ale zwykle tego chcesz .

LET sugeruje, że robisz standardowe wiązanie równoległe i nie dzieje się nic trudnego. LET * wprowadza ograniczenia w kompilatorze i sugeruje użytkownikowi, że istnieje powód, dla którego potrzebne są sekwencyjne wiązania. Pod względem stylu LET jest lepszy, gdy nie potrzebujesz dodatkowych ograniczeń nałożonych przez LET *.

Bardziej efektywne może być użycie LET niż LET * (w zależności od kompilatora, optymalizatora itp.):

  • powiązania równoległe mogą być wykonywane równolegle (ale nie wiem, czy któryś z systemów LISP faktycznie to robi, a formularze init muszą być nadal wykonywane sekwencyjnie)
  • powiązania równoległe tworzą jedno nowe środowisko (zakres) dla wszystkich powiązań. Powiązania sekwencyjne tworzą nowe środowisko zagnieżdżone dla każdego powiązania. Powiązania równoległe zużywają mniej pamięci i mają szybsze wyszukiwanie zmiennych .

(Powyższe punkty dotyczą Scheme, innego dialektu LISP. Clisp może się różnić).


2
Uwaga: Zapoznaj się z tą odpowiedzią (i / lub połączoną sekcją hiperspeku), aby uzyskać trochę wyjaśnienia, dlaczego twój pierwszy punkt na kury jest - powiedzmy, mylący. Do Wiązania zdarzyć równolegle, ale postacie są wykonywane sekwencyjnie - za spec.
Lindes

6
wykonywanie równoległe nie jest czymś, o co w żaden sposób dba standard Common Lisp. Szybsze wyszukiwanie zmiennych również jest mitem.
Rainer Joswig,

1
Różnica jest ważna nie tylko dla kompilatora. Używam let i let * jako wskazówek dla siebie, co się dzieje. Kiedy widzę let w moim kodzie, wiem, że powiązania są niezależne, a kiedy widzę let *, wiem, że powiązania zależą od siebie. Ale wiem to tylko dlatego, że używam let i let * konsekwentnie.
Jeremiah,

28

Przychodzę z wymyślonymi przykładami. Porównaj wynik tego:

(print (let ((c 1))
         (let ((c 2)
               (a (+ c 1)))
           a)))

z wynikiem uruchomienia tego:

(print (let ((c 1))
         (let* ((c 2)
                (a (+ c 1)))
           a)))

3
Chcesz dowiedzieć się, dlaczego tak jest?
John McAleely

4
@John: w pierwszym przykładzie awiązanie odnosi się do zewnętrznej wartości c. W drugim przykładzie, gdzie let*pozwala powiązaniom odwoływać się do poprzednich powiązań, apowiązanie odnosi się do wewnętrznej wartości c. Logan nie kłamie, że to wymyślony przykład i nawet nie udaje, że jest przydatny. Ponadto wcięcie jest niestandardowe i wprowadza w błąd. W obu przypadkach awiązanie powinno znajdować się o jedną spację powyżej, aby wyrównać je z c„s”, a „ciało” wewnętrznego letpowinno znajdować się zaledwie dwie spacje od letsamego siebie.
zem

3
Ta odpowiedź dostarcza ważnych informacji. Można by specjalnie używać let, gdy chce się uniknąć konieczności wiązania wtórne (przez co nie znaczy, ja po prostu pierwszy z nich) odnoszą się do pierwszego wiązania, ale nie chcą cień poprzedniego wiązania - używając poprzednią wartość to za inicjowanie jedną Twoje dodatkowe powiązania.
Lindes

2
Chociaż wcięcie jest wyłączone (i nie mogę go edytować), jest to jak dotąd najlepszy przykład. Kiedy patrzę na (niech ...) wiem, że żadne z wiązań nie będzie się budować od siebie i można je traktować oddzielnie. Kiedy patrzę na (niech * ...), zawsze podchodzę ostrożnie i bardzo uważnie sprawdzam, które wiązania są ponownie używane. Tylko z tego powodu sensowne jest używanie (let), chyba że absolutnie potrzebujesz zagnieżdżenia.
andrew

1
(6 lat później ...) Czy błędne i wprowadzające w błąd wcięcie zostało zaprojektowane jako pułapka ? Jestem skłonny go edytować, żeby to naprawić ... Czy nie powinienem?
Will Ness

11

W LISP-ie często istnieje chęć użycia najsłabszych możliwych konstrukcji. Niektóre przewodniki stylistyczne podpowiadają, aby używać =zamiast eql, na przykład, gdy wiesz, że porównywane elementy są numeryczne. Często chodzi o to, aby sprecyzować, co masz na myśli, zamiast efektywnie programować komputer.

Jednak rzeczywistą poprawę wydajności może przynieść mówienie tylko o tym, co masz na myśli, a nie używanie silniejszych konstrukcji. Jeśli masz inicjalizacje z LET, mogą być wykonywane równolegle, podczas gdy LET*inicjalizacje muszą być wykonywane sekwencyjnie. Nie wiem, czy jakieś implementacje faktycznie to zrobią, ale niektóre mogą równie dobrze w przyszłości.


2
Słuszna uwaga. Chociaż Lisp jest językiem wysokiego poziomu, zastanawiam się, dlaczego „najsłabsze możliwe konstrukcje” są tak pożądanym stylem w krainie Lispa. Nie widzisz programistów Perla, którzy mówią „cóż, nie musimy tutaj używać wyrażenia regularnego…” :-)
Ken

1
Nie wiem, ale istnieje zdecydowana preferencja stylu. Nieco temu przeciwstawiają się ludzie (tacy jak ja), którzy lubią używać tej samej formy tak często, jak to możliwe (prawie nigdy nie piszę setq zamiast setf). Może to mieć coś wspólnego z pomysłem powiedzenia, co masz na myśli.
David Thornley

=Operator nie jest ani silniejszy, ani słabszy niż eql. Jest to słabszy test, ponieważ 0jest równy 0.0. Ale jest też silniejszy, ponieważ odrzuca się argumenty nieliczbowe.
Kaz

2
Zasada, do której się odnosisz, to użycie najsilniejszego możliwego do zastosowania prymitywu, a nie najsłabszego . Na przykład, jeśli porównywane rzeczy są symbolami, użyj eq. Lub jeśli wiesz, że przypisujesz symboliczne miejsce, użyj setq. Jednak ta zasada jest również odrzucana przez wielu moich programistów Lispa, którzy chcą po prostu języka wysokiego poziomu bez przedwczesnej optymalizacji.
Kaz

1
właściwie, CLHS mówi " powiązania [są wykonywane] równolegle", ale "wyrażenia init-form-1 , init-form-2 i tak dalej, [są oceniane] w [specyficzne, od lewej do prawej (lub na górze -down)] order ”. Dlatego wartości muszą być obliczane sekwencyjnie (powiązania są ustanawiane po obliczeniu wszystkich wartości). Ma to również sens, ponieważ podobna do RPLACD mutacja strukturalna jest częścią języka i przy prawdziwym równoległości stałaby się niedeterministyczna.
Will Ness

9

Ostatnio pisałem funkcję dwóch argumentów, w której algorytm jest wyrażany najwyraźniej, jeśli wiemy, który argument jest większy.

(defun foo (a b)
  (let ((a (max a b))
        (b (min a b)))
    ; here we know b is not larger
    ...)
  ; we can use the original identities of a and b here
  ; (perhaps to determine the order of the results)
  ...)

Przypuśćmy, bbyła większa, gdybyśmy używany let*, mielibyśmy przypadkowo ustawione ai bdo tej samej wartości.


1
O ile nie będziesz potrzebować wartości x i y później wewnątrz zewnętrznej strony let, można to zrobić prościej (i wyraźnie) za pomocą: (rotatef x y)- niezły pomysł, ale nadal wydaje się, że to rozciągnięcie.
Ken

To prawda. Byłoby bardziej przydatne, gdyby x i y były zmiennymi specjalnymi.
Samuel Edwin Ward

9

Główną różnicą w Common List pomiędzy LET i LET * jest to, że symbole w LET są połączone równolegle, aw LET * są powiązane sekwencyjnie. Użycie LET nie pozwala na równoległe wykonywanie formularzy init ani na zmianę kolejności formularzy init. Powodem jest to, że Common Lisp pozwala funkcjom wywoływać skutki uboczne. Dlatego kolejność ocen jest ważna i zawsze w formularzu jest od lewej do prawej. Tak więc w LET, formularze init są oceniane jako pierwsze, od lewej do prawej, a następnie tworzone są wiązania, równolegle od lewej do prawej . W LET * obliczana jest forma inicjująca, a następnie wiązana z symbolem po kolei, od lewej do prawej.

CLHS: Operator specjalny LET, LET *


2
Wygląda na to, że ta odpowiedź może czerpać energię z bycia odpowiedzią na tę odpowiedź ? Ponadto, zgodnie ze specyfikacją, powiązania są wykonywane równolegle LET, nawet jeśli masz rację, mówiąc , że formularze init są wykonywane szeregowo. Nie wiem, czy ma to jakąś praktyczną różnicę w jakiejkolwiek istniejącej implementacji.
Lindes

8

I pójść o krok dalej i użyć wiążą która jednoczy let, let*, multiple-value-bind, destructuring-binditd., i to nawet rozszerzalny.

ogólnie lubię używać „najsłabszej konstrukcji”, ale nie z let& przyjaciółmi, ponieważ po prostu zakłócają kod (ostrzeżenie o subiektywności! nie ma potrzeby przekonywania mnie o czymś przeciwnym ...)


Och, schludnie. Zamierzam teraz pobawić się BIND. Dzięki za link!
Ken

4

Przypuszczalnie użycie letkompilatora zapewnia większą elastyczność w zmianie kolejności kodu, być może w celu zwiększenia przestrzeni lub szybkości.

Pod względem stylistycznym użycie równoległych powiązań wskazuje na zamiar zgrupowania powiązań; jest to czasami używane do zachowywania dynamicznych powiązań:

(let ((*PRINT-LEVEL* *PRINT-LEVEL*)
      (*PRINT-LENGTH* *PRINT-LENGTH*))
  (call-functions that muck with the above dynamic variables)) 

W 90% kodu nie ma znaczenia, czy używasz, LETczy LET*. Więc jeśli używasz *, dodajesz niepotrzebny glif. Gdyby LET*był to segregator równoległy i LETbyłby to segregator szeregowy, programiści nadal używaliby go LETi wyciągaliby tylko LET*wtedy , gdy chcą równoległego wiązania. To prawdopodobnie spowodowałoby LET*rzadkość.
Kaz

w rzeczywistości CLHS określa kolejność oceny init-form s.
Will Ness

4
(let ((list (cdr list))
      (pivot (car list)))
  ;quicksort
 )

Oczywiście to zadziałałoby:

(let* ((rest (cdr list))
       (pivot (car list)))
  ;quicksort
 )

I to:

(let* ((pivot (car list))
       (list (cdr list)))
  ;quicksort
 )

Ale liczy się myśl.


2

OP pyta „kiedykolwiek potrzebował LET”?

Kiedy utworzono Common Lisp, załadowano istniejący kod Lisp w różnych dialektach. Założeniem, które zaakceptowali twórcy Common Lisp, było stworzenie dialektu Lispa, który zapewniłby wspólną płaszczyznę. „Potrzebowali”, aby portowanie istniejącego kodu do Common Lisp było łatwe i atrakcyjne. Pozostawienie LET lub LET * poza językiem mogłoby służyć innym cnotom, ale zignorowałoby ten kluczowy cel.

Używam LET zamiast LET *, ponieważ mówi czytelnikowi o tym, jak rozwija się przepływ danych. Przynajmniej w moim kodzie, jeśli widzisz LET *, wiesz, że wartości związane wcześnie zostaną użyte w późniejszym powiązaniu. Czy „muszę” to zrobić, nie; ale myślę, że to pomocne. To powiedziawszy, rzadko czytałem kod domyślny LET *, a pojawienie się LET sygnalizuje, że autor naprawdę tego chciał. To znaczy, na przykład, aby zamienić znaczenie dwóch zmiennych.

(let ((good bad)
     (bad good)
...)

Istnieje dyskusyjny scenariusz, który zbliża się do „rzeczywistych potrzeb”. Powstaje z makrami. To makro:

(defmacro M1 (a b c)
 `(let ((a ,a)
        (b ,b)
        (c ,c))
    (f a b c)))

działa lepiej niż

(defmacro M2 (a b c)
  `(let* ((a ,a)
          (b ,b)
          (c ,c))
    (f a b c)))

ponieważ (M2 cba) nie zadziała. Ale te makra są dość niechlujne z różnych powodów; co podważa argument „rzeczywistej potrzeby”.


1

Oprócz odpowiedzi Rainera Joswiga iz purystycznego lub teoretycznego punktu widzenia. Niech & Let * reprezentuje dwa paradygmaty programowania; odpowiednio funkcjonalne i sekwencyjne.

A jeśli chodzi o to, dlaczego powinienem po prostu używać Let * zamiast Let, no cóż, odbierasz mi radość z powrotu do domu i myślenia czysto funkcjonalnym językiem, w przeciwieństwie do języka sekwencyjnego, z którym spędzam większość dnia na pracy :)


1

Z Pozwól używać łączenia równoległego,

(setq my-pi 3.1415)

(let ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3.1415)

A dzięki łączeniu szeregowemu Let *,

(setq my-pi 3.1415)

(let* ((my-pi 3) (old-pi my-pi))
     (list my-pi old-pi))
=> (3 3)

Tak, tak są zdefiniowane. Ale kiedy potrzebujesz tego pierwszego? Zakładam, że nie piszesz programu, który musi zmieniać wartość liczby pi w określonej kolejności. :-)
Ken

1

letOperator wprowadza jednolite środowisko dla wszystkich powiązań, które to określa. let*, przynajmniej koncepcyjnie (i jeśli przez chwilę zignorujemy deklaracje) wprowadza wiele środowisk:

To jest do powiedzenia:

(let* (a b c) ...)

jest jak:

(let (a) (let (b) (let (c) ...)))

Więc w pewnym sensie letjest bardziej prymitywny, podczas gdy let*jest cukrem syntaktycznym do pisania kaskady let-s.

Ale nieważne. (A poniżej podam uzasadnienie, dlaczego nie powinniśmy „nieważne”). Faktem jest, że istnieją dwa operatory iw „99%” kodu nie ma znaczenia, którego użyjesz. Powodem do preferowania letprzez let*to po prostu, że nie ma *wiszących na końcu.

Jeśli masz dwa operatory, a jeden z nich ma *zwisającą nazwę, użyj tego bez, *jeśli działa w takiej sytuacji, aby kod był mniej brzydki.

To wszystko.

Biorąc to pod uwagę, podejrzewam, że gdyby leti let*wymienili ich znaczenia, prawdopodobnie byłoby to rzadziej używane let*niż teraz. Zachowanie wiązania szeregowego nie będzie przeszkadzać większości kodu, który tego nie wymaga: rzadko musiałoby let*być używane do żądania zachowania równoległego (a takie sytuacje można również naprawić, zmieniając nazwy zmiennych, aby uniknąć cieniowania).

A teraz ta obiecana dyskusja. Chociaż let*koncepcyjnie wprowadza wiele środowisk, bardzo łatwo jest skompilować let*konstrukcję, aby wygenerować jedno środowisko. Więc nawet jeśli (przynajmniej jeśli zignorujemy deklaracje ANSI CL), istnieje algebraiczna równość, że pojedynczy let*odpowiada wielu zagnieżdżonym let-s, co sprawia, że letwygląda bardziej prymitywnie, nie ma powodu, aby faktycznie rozszerzać let*go letdo kompilacji, a nawet zły pomysł.

Jeszcze jedno: zauważ, że Common Lisp lambdafaktycznie używa let*semantyki -podobnie jak! Przykład:

(lambda (x &optional (y x) (z (+1 y)) ...)

tutaj xinit-form do ydostępu do wcześniejszego xparametru i podobnie (+1 y)odwołuje się do wcześniejszego yopcjonalnego. W tym obszarze języka widać wyraźną preferencję dla sekwencyjnej widoczności w powiązaniach. Byłoby mniej przydatne, gdyby formularze w a lambdanie widziały parametrów; opcjonalny parametr nie mógł być zwięźle domyślny pod względem wartości poprzednich parametrów.


0

W obszarze letwszystkie wyrażenia inicjalizujące zmienne widzą dokładnie to samo środowisko leksykalne: to, które otacza let. Jeśli te wyrażenia przechwytują domknięcia leksykalne, wszystkie mogą współużytkować ten sam obiekt środowiska.

Poniżej let*każde wyrażenie inicjujące znajduje się w innym środowisku. Dla każdego kolejnego wyrażenia należy rozszerzyć środowisko, aby utworzyć nowe. Przynajmniej w semantyce abstrakcyjnej, jeśli przechwytuje się domknięcia, mają one różne obiekty środowiska.

let*Musi być dobrze zoptymalizowany, aby zwinąć niepotrzebne rozszerzenia środowiska w celu nadaje się jako zamiennik dla codziennego let. Musi istnieć kompilator, który działa, które formy uzyskują dostęp, a następnie konwertuje wszystkie niezależne na większe, połączone let.

(Jest to prawdą, nawet jeśli let*jest to tylko operator makra, który emituje letformularze kaskadowe ; optymalizacja jest wykonywana na tych kaskadowych let).

Nie możesz zaimplementować let*jako pojedynczy naiwny let, z ukrytymi przypisaniami zmiennych do wykonania inicjalizacji, ponieważ zostanie ujawniony brak odpowiedniego zakresu:

(let* ((a (+ 2 b))  ;; b is visible in surrounding env
       (b (+ 3 a)))
  forms)

Jeśli to zostanie zamienione na

(let (a b)
  (setf a (+ 2 b)
        b (+ 3 a))
  forms)

to nie zadziała w tym przypadku; wewnętrzna część bzacienia zewnętrzną, bwięc w końcu dodajemy 2 do nil. Tego rodzaju transformację można przeprowadzić, jeśli zmienimy alfabetycznie nazwy wszystkich tych zmiennych. Środowisko jest wtedy ładnie spłaszczone:

(let (#:g01 #:g02)
  (setf #:g01 (+ 2 b) ;; outer b, no problem
        #:g02 (+ 3 #:g01))
  alpha-renamed-forms) ;; a and b replaced by #:g01 and #:g02

W tym celu musimy rozważyć obsługę debugowania; jeśli programista wkracza do tego zakresu leksykalnego za pomocą debugera, czy chcemy, aby zajmował się nim #:g01zamiast a.

Zasadniczo let*jest to skomplikowana konstrukcja, którą należy dobrze zoptymalizować, aby działała, a także letw przypadkach, gdy można ją zredukować do let.

Że sam nie uzasadnia sprzyjanie letnad let*. Załóżmy, że mamy dobry kompilator; dlaczego nie używać let*cały czas?

Zgodnie z ogólną zasadą powinniśmy faworyzować konstrukcje wyższego poziomu, które czynią nas produktywnymi i redukują błędy, zamiast podatnych na błędy konstrukcje niższego poziomu i polegać w jak największym stopniu na dobrych implementacjach konstrukcji wyższego poziomu, aby rzadko musieliśmy poświęcać ich wykorzystanie dla celów wydajnościowych. Dlatego przede wszystkim pracujemy w języku takim jak Lisp.

To rozumowanie nie pasuje dobrze do letversus let*, ponieważ let*nie jest wyraźnie wyższym poziomem abstrakcji w stosunku do let. Są mniej więcej na „równym poziomie”. Dzięki let*, możesz wprowadzić błąd, który można rozwiązać, po prostu przełączając się na let. I odwrotnie . let*tak naprawdę jest tylko łagodnym cukrem syntaktycznym do wizualnego zapadania się letzagnieżdżenia, a nie znaczącą nową abstrakcją.


-1

Najczęściej używam LET, chyba że konkretnie potrzebuję LET *, ale czasami piszę kod, który wyraźnie potrzebuje LET, zwykle podczas wykonywania różnych (zwykle skomplikowanych) domyślnych ustawień . Niestety nie mam pod ręką żadnego przydatnego przykładu kodu.


-1

kto ma ochotę ponownie napisać letf vs letf *? liczba połączeń typu „relax-protect”?

łatwiejsze do optymalizacji powiązań sekwencyjnych.

może to wpływa na env ?

pozwala na kontynuacje z dynamicznym zakresem?

czasami (let (xyz) (setq z 0 y 1 x (+ (setq x 1) (prog1 (+ xy) (setq x (1- x))))) (wartości ()))

[Myślę, że to działa] Rzecz w tym, że czasami prostsze jest łatwiejsze do odczytania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.