Jakie są potencjalne pułapki włączenia wiązania leksykalnego dla bufora?

12

Zostało to zainspirowane dyskusją na temat wiązania leksykalnego a leksykalno-letniego w tym pytaniu . Jak leksykalno-wiązanie daje możliwość posiadania użytecznych closures ludzie mogą być wykorzystane w innych językach takich jak JavaScript, dlaczego nie włączono go cały czas?

Zakładając, że wsteczna kompatybilność ze starszą wersją Emacsena nie stanowi problemu, jakich pułapek powinieneś szukać, jeśli włączasz ją w starszych buforach kodu?

stsquad
źródło

Odpowiedzi:

13

Główną pułapką jest to, że semantyka wiązania dla niezdefiniowanych zmiennych - tj. Zmiennych niezdefiniowanych przez defvari znajomych - zmienia się z lexical-binding: Bez tego letwiąże wszystko dynamicznie, ale z lexical-bindingwłączonymi niezdefiniowanymi zmiennymi jest powiązana leksykalnie , a nawet całkowicie pomijana, jeśli nie jest używana w bieżącym zakresie leksykalnym .

Stary kod czasami opiera się na tym. Aby uniknąć twardych zależności dla funkcji opcjonalnych, wiązałoby zmienne dynamiczne bez wymagania odpowiedniej biblioteki lub deklarowania samej zmiennej:

(let ((cook-eggs-enabled t))
  (cook-my-meal))

Jeśli funkcja gotowania jest opcjonalna, nie chcemy narzucać użytkownikowi niepotrzebnych zależności, więc nie używamy, (require 'cook)a zamiast tego polegamy na automatycznym ładowaniu cook-my-mealfunkcji.

Dla ludzkiego czytelnika cook-eggs-enabledjest oczywiste, że nie jest to zmienna lokalna, ale wciąż odnosi się do jakiejś globalnej zmiennej dynamicznej z cookbiblioteki tutaj. Bez lexical-bindingtego kodu działa zgodnie z przeznaczeniem: cook-eggs-enabledjest związany dynamicznie, czy określona lub nie.

Dzięki lexical-bindingjednak łamie: cook-eggs-enabledjest teraz związany leksykalnie (a następnie optymalizowane daleko, ponieważ nie jest używany), więc zmienna globalna dynamiczny cook-eggs-enabledjest nie kiedykolwiek dotknęła w ogóle i jeszcze nilprzez czas cook-my-mealnazywa, więc zaskakująco nie będzie miał żadnych jaj w naszym posiłku.

Na szczęście te problemy są bardzo łatwe do wykrycia : kompilator bajtów naturalnie ostrzega przed nieużywanym powiązaniem leksykalnym tutaj.

Poprawka jest prosta: albo dodaj (require 'cook)(dla funkcji, które tak naprawdę nie są opcjonalne), albo - aby uniknąć twardych zależności - zadeklaruj zmienną jako zmienną dynamiczną we własnym kodzie . Istnieje defvardo tego specjalny formularz:

(defvar cook-eggs-enabled)

Definiuje to cook-eggs-enabledjako zmienną dynamiczną, ale nie wpływa na dokumentację, load-history(a więc find-variablei znajomych) ani nic innego, z wyjątkiem wiążącej natury zmiennej.

księżycowy
źródło
W dynamicznej sytuacji nie że fragment kodu przyczyna cook-eggs-enabledbyć niezwiązany kiedy letkończy? Jestem prawie pewien, że napotkałem już taki błąd. Defvar działał wewnątrz let, a letpóźniej przywrócił zmienną do jej początkowego (pustego) stanu.
Malabarba,
1
@Malababa Nie, to inna sytuacja. Powód znajduje się w ostatnim akapicie Definiowanie zmiennych .
lunaryorn