Defun inside let z powiązaniem leksykalnym daje ostrzeżenie kompilacji bajtów „funkcja nie jest zdefiniowana”

13

Chcę uzyskać efekt zmiennej statycznej, używając defunwewnątrz letz leksykalnym wiązaniem do utworzenia zamknięcia. Jednak podczas kompilacji bajtowej pliku pojawia się ostrzeżenie. Czy robię coś złego, a jeśli nie, czy istnieje sposób na stłumienie tego ostrzeżenia?

Stworzyłem MCVE:

;; -*- lexical-binding: t -*-

(let ((count 0))
  (defun increase-count ()
    (interactive)
    (setq count (1+ count))
    (message "Count is: %d" count))

  ;; The warning happens here.
  (increase-count))

Kod działa zgodnie z oczekiwaniami: funkcja wyświetla increase-count„Count is: n”, gdzie n zwiększa się za każdym razem, gdy jest wywoływane. Jednak podczas kompilacji bajtowej tego pliku pojawia się następujące ostrzeżenie:

In end of data:
mcve.el:11:1:Warning: the function ‘increase-count’ is not known to be
    defined.

Wydaje mi się, że increase-countnależy to zawsze zdefiniować, zanim zostanie wywołane na końcu let-block. Czy tak nie jest?

Will Kunkel
źródło
defunnie robi tego, co myślisz, zawsze tworzy definicję najwyższego poziomu. W końcu Elisp nie jest Schematem ...
wasamasa,
2
Wiem, że tworzy definicję najwyższego poziomu; to jest to czego chce. Chcę tylko, aby ta definicja najwyższego poziomu była zamknięciem. Wydaje się, że działa tak, jak chcę, z wyjątkiem tego ostrzeżenia o kompilacji bajtów.
Will Kunkel,

Odpowiedzi:

7

Sposób kompilatora bajtów decydujący o tym, czy funkcja zostanie zdefiniowana, jest bardzo „naiwny” i daje się zwieść nawet w „oczywistym” przypadku. Ale możesz napisać to w sposób, który pozwoli kompilatorowi zrozumieć, co się dzieje:

(defalias 'increase-count
  (let ((count 0))
    (lambda ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

Oczywiście jeszcze lepszym rozwiązaniem byłoby usprawnienie logiki kompilatora bajtów: mile widziane łatki.

Stefan
źródło
5

Aby pominąć ostrzeżenie kompilatora bajtów, spróbuj dodać to przed kodem, zaczynając od kolumny 0 (skrajnie po lewej):

(declare-function increase-count "your-file-name.el")

C-h f declare-function mówi ci:

declare-functionto makro Lisp w subr.el.

(declare-function FN FILE &optional ARGLIST FILEONLY)

Powiedz kompilatorowi bajtów, że funkcja FNjest zdefiniowana w FILE. FILEArgument nie jest używany przez bajt kompilatora, ale do check-declarepakietu, który sprawdza, czy plik zawiera definicję FN.

FILEmoże być albo plikiem Lisp (w takim przypadku ".el" rozszerzenie jest opcjonalne), albo plikiem C. Pliki C są rozwijane względem "src/"katalogu Emacsa . Pliki Lisp są wyszukiwane za pomocą locate-library, a jeśli to się nie powiedzie, są one rozwijane względem lokalizacji pliku zawierającego deklarację. A FILEz "ext:"prefiksem to plik zewnętrzny. check-declaresprawdzi takie pliki, jeśli zostaną znalezione, i pomiń je bez błędów, jeśli nie są.

Opcjonalne ARGLISTokreśla FNargumenty lub tnie określa FNargumentów. Pominięte ARGLISTdomyślnie to t, a nie nil: a nil ARGLISTokreśla pustą listę argumentów, a jawne t ARGLISTjest symbolem zastępczym, który pozwala na podanie późniejszego argumentu.

Opcjonalne FILEONLYnie niloznacza, że check-declaresprawdzi, czy FILEistnieje, a nie definiuje FN. Jest to przeznaczone dla definicji funkcji, które check-declarenie rozpoznają, np defstruct.

Zauważ, że do celów check-declareta instrukcja musi być pierwszą spacją w linii.

Aby uzyskać więcej informacji, zobacz Węzeł informacji (elisp)Declaring Functions.

Rysował
źródło
Czy w FILEONLYprzedmiotowej sprawie niezbędny jest argument inny niż zero ? BTW, dałbym tę samą odpowiedź ;-).
Tobias
@Tobias: FILEONLYWydaje mi się, że tutaj nie był potrzebny. Co wydaje się wskazywać, że check-declarerozpoznaje fi godrzuca.
Drew
@Drew, myślę, że ostatni komentarz na temat fi gma sens tylko w kontekście emacs.stackexchange.com/q/39439 ?
phils
@phils: Tak, chciałem to powiedzieć: FILEONLYnie wydawało mi się to potrzebne tutaj. Co wydaje się wskazywać, że check-declarerozpoznaje increase-countprzegraną. ;-)
Drew
3

Sądzę, że umieszczenie omawianej definicji w ten sposób eval-and-compilemogłoby pozornie osiągnąć ten sam wynik, co w poprawnej odpowiedzi Stefana :

(eval-and-compile
  (let ((count 0))
    (defun increase-count ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

Jednak ledwo znam subtelności korzystania, eval-and-compilea ponadto nie oczekuję, że takie podejście będzie w jakikolwiek sposób lepsze.

Bazylia
źródło