Kann eine Funktion oder ein Makro Byte-Compiler-Warnungen angeben?

15

Ich schreibe eine Funktion, die im Prinzip eine beliebige Anzahl von Argumenten akzeptiert. In der Praxis sollte jedoch immer nur eine gerade Anzahl von Argumenten übergeben werden, und ansonsten werden unerwünschte Ergebnisse erzielt.

Hier ist ein Dummy-Beispiel für den Kontext:

(defun my-caller (&rest args)
  (while args
    (call-other-function (pop args) (pop args))))

Wenn eine Elisp-Datei bytemäßig kompiliert wird, gibt der Byte-Compiler eine Warnung aus, wenn eine Funktion mit der falschen Anzahl von Argumenten aufgerufen wird. Offensichtlich wird das nie passieren my-caller, da es so definiert ist, dass es eine beliebige Anzahl annehmen kann.

Vielleicht gibt es noch eine Symboleigenschaft, die ich festlegen kann, oder ein (declare)Formular, das ich zu seiner Definition hinzufügen kann. Etwas, das den Benutzer darauf hinweist, dass diese Funktion nur mit einer geraden Anzahl von Argumenten versehen werden sollte.

  1. Gibt es eine Möglichkeit, den Byte-Compiler über diese Einschränkung zu informieren?
  2. Wenn nicht, ist es mit einem Makro anstelle einer Funktion möglich?
Malabarba
quelle
msgstr "... wenn eine Funktion mit der falschen Anzahl von Argumenten aufgerufen wird "?
Es ist der

Antworten:

13

BEARBEITEN : Eine bessere Möglichkeit, dies in neueren Emacs zu tun, besteht darin, ein Compiler-Makro zu definieren , um die Anzahl der Argumente zu überprüfen. Meine ursprüngliche Antwort unter Verwendung eines normalen Makros wird im Folgenden beibehalten, aber ein Compiler-Makro ist überlegen, da es die Übergabe der Funktion an funcalloder applyzur Laufzeit nicht verhindert .

In neueren Versionen von Emacs können Sie dazu ein Compiler-Makro für Ihre Funktion definieren, das die Anzahl der Argumente überprüft und eine Warnung (oder sogar einen Fehler) ausgibt, wenn diese nicht übereinstimmt. Die einzige feine Sache ist, dass das Compiler-Makro das ursprüngliche Funktionsaufrufformular zur Auswertung oder Kompilierung unverändert zurückgeben soll. Dazu wird ein &wholeArgument verwendet und dessen Wert zurückgegeben. Dies könnte folgendermaßen geschehen:

(require 'cl-lib)

(defun my-caller (&rest args)
  (while args
    (message "%S %S" (pop args) (pop args))))

(define-compiler-macro my-caller (&whole form &rest args)
  (when (not (cl-evenp (length args)))
    (byte-compile-warn "`my-caller' requires an even number of arguments"))
  form)

(my-caller 1 2 3 4)
(my-caller 1 2)
(funcall #'my-caller 1 2 3 4)       ; ok
(apply #'my-caller '(1 2))          ; also ok
(my-caller 1)                       ; produces a warning
(funcall #'my-caller 1 2 3)         ; no warning!
(apply #'my-caller '(1 2 3))        ; also no warning

Beachten Sie, dass funcallund applyjetzt verwendet werden können, sie jedoch die Argumentprüfung durch das Compiler-Makro umgehen. Trotz ihres Namens scheinen Compiler-Makros auch im Zuge der 'interpretierten' Auswertung über erweitert zu C-xC-ewerden M-xeval-buffer, so dass Sie Fehler beim Auswerten sowie beim Kompilieren dieses Beispiels bekommen.


Die ursprüngliche Antwort lautet:

Hier sehen Sie, wie Sie Jordons Vorschlag umsetzen können, "ein Makro zu verwenden, das beim Erweitern Warnungen ausgibt". Es stellt sich als sehr einfach heraus:

(require 'cl-lib)

(defmacro my-caller (&rest args)
  (if (cl-evenp (length args))
      `(my-caller--function ,@args)
    (error "Function `my-caller' requires an even number of arguments")))

(defun my-caller--function (&rest args)
  ;; function body goes here
  args)

(my-caller 1 2 3 4)
(my-caller 1 2 3)

Der Versuch, die obigen Informationen in einer Datei zu kompilieren, schlägt fehl (es wird keine .elcDatei erstellt). Im Kompilierungsprotokoll wird eine anklickbare Fehlermeldung angezeigt, die Folgendes angibt:

test.el:14:1:Error: `my-caller' requires an even number of arguments

Sie könnten auch ersetzen (error …)mit (byte-compile-warn …)einer Warnung statt einem Fehler zu erzeugen, so dass Kompilation fortzusetzen. (Danke an Jordon für den Hinweis in den Kommentaren).

Da Makros zur Kompilierungszeit erweitert werden, ist mit dieser Prüfung keine Laufzeitstrafe verbunden. Natürlich können Sie andere Personen nicht davon abhalten, my-caller--functiondirekt anzurufen , aber Sie können es zumindest als "private" Funktion unter Verwendung der Konvention mit zwei Bindestrichen bewerben.

Ein bemerkenswerter Nachteil der Verwendung eines Makros für diesen Zweck ist, dass my-calleres keine erstklassige Funktion mehr ist: Sie können es nicht an funcalloder applyzur Laufzeit übergeben (oder es wird zumindest nicht das tun, was Sie erwarten). Insofern ist diese Lösung nicht ganz so gut, als eine Compiler-Warnung für eine echte Funktion deklarieren zu können. Die Verwendung von applywürde es natürlich unmöglich machen, die Anzahl der Argumente zu überprüfen, die zur Kompilierungszeit an die Funktion übergeben werden. Vielleicht ist dies also ein akzeptabler Kompromiss.

Jon O.
quelle
2
Kompilierungswarnungen werden erstellt mitbyte-compile-warn
Jordon Biondo
Ich frage mich jetzt, ob dies effektiver erreicht werden könnte, indem ein Compiler-Makro für die Funktion definiert wird. Dies würde beseitigen den Nachteil, nicht zu sein applyoder funcallden Makro - Wrapper. Ich werde es ausprobieren und meine Antwort bearbeiten, wenn es funktioniert.
Jon O.
11

Ja, das kannst du benutzen byte-defop-compiler von eine Funktion angeben, die Ihre Funktion kompiliert. Es byte-defop-compilersind einige nützliche Funktionen integriert, mit denen Sie festlegen können, dass Ihre Funktionen basierend auf einer Reihe von Argumenten Warnungen ausgeben sollen.

Dokumentation

Fügen Sie eine Compiler-Form für FUNCTION hinzu. Wenn function ein Symbol ist, muss die Variable "byte-SYMBOL" den zu verwendenden Opcode benennen. Wenn function eine Liste ist, ist das erste Element die Funktion und das zweite Element das Bytecode-Symbol. Das zweite Element kann null sein, was bedeutet, dass es keinen Opcode gibt. COMPILE-HANDLER ist die Funktion, die zum Kompilieren dieses Byte-Ops verwendet wird, oder kann die Abkürzungen 0, 1, 2, 3, 0-1 oder 1-2 haben. Wenn es Null ist, dann ist der Handler "Byte-Compile-SYMBOL".


Verwendung

In Ihrem speziellen Fall können Sie eine der Abkürzungen verwenden, um zu definieren, dass Ihre Funktion zwei Argumente erhalten soll.

(byte-defop-compiler my-caller 2)

Jetzt gibt Ihre Funktion Warnungen aus, wenn sie mit etwas anderem als 2 Argumenten kompiliert wird.

Wenn Sie spezifischere Warnungen geben und Ihre eigenen Compilerfunktionen schreiben möchten. Schauen Sie sich byte-compile-one-argund andere ähnliche Funktionen in bytecomp.el als Referenz an.

Beachten Sie, dass Sie nicht nur eine Funktion für die Validierung angeben, sondern auch das Kompilieren. Wieder Kompilierungsfunktionen in bytecomp.el bieten Ihnen eine gute Referenz.


Sicherere Routen

Dies ist nicht etwas, was ich online dokumentiert oder diskutiert habe, aber insgesamt würde ich sagen, dass dies ein schlechter Weg ist. Der richtige Weg (IMO) wäre, Ihre Defuns mit beschreibenden Signaturen zu schreiben oder ein Makro zu verwenden, das zur Expansionszeit Warnungen ausgibt, die Länge Ihrer Argumente überprüft und mit byte-compile-warnoder errorFehler anzeigt . Es kann auch von Vorteil sein, wenn Sie davon Gebrauch macheneval-when-compile , Fehlerprüfungen durchzuführen.

Sie müssen auch Ihre Funktion definieren, bevor sie jemals verwendet wird, und den Aufruf an byte-defop-compiler muss erfolgen, bevor der Compiler zu den tatsächlichen Aufrufen Ihrer Funktion gelangt.

Auch hier scheint es nicht wirklich dokumentiert oder empfohlen zu sein, was ich gesehen habe (könnte falsch sein), aber ich stelle mir vor, dass das hier zu befolgende Muster darin besteht, eine Art Header-Datei für Ihr Paket anzugeben, die voll von leeren Defuns ist und ruft an byte-defop-compiler . Dies wäre im Grunde ein Paket, das benötigt wird, bevor Ihr echtes Paket kompiliert werden kann.

Meinung: Aufgrund meines Wissens, das nicht viel ist, weil ich gerade von all dem erfahren habe, würde ich Ihnen raten, nichts davon zu tun. je

Jordon Biondo
quelle
1
Verwandte: Es gibt bytecomp-simplify, das dem Byte-Compiler zusätzliche Warnungen beibringt.
Wilfred Hughes