Una funzione o una macro può specificare avvisi per il compilatore di byte?


15

Sto scrivendo una funzione che, in linea di principio, accetta un numero arbitrario di argomenti. In pratica, tuttavia, si deve sempre e solo essere passato un ancor numero di argomenti, e produrrà risultati indesiderati altrimenti.

Ecco un esempio fittizio per il contesto:

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

Quando un file elisp viene compilato in byte, il compilatore di byte genera un avviso quando vede una funzione invocata con un numero errato di argomenti. Ovviamente, questo non accadrà mai my-caller, poiché è definito per prendere qualsiasi numero.

Tuttavia, forse c'è una proprietà del simbolo che posso impostare o un (declare)modulo che posso aggiungere alla sua definizione. Qualcosa per avvisare l'utente che a questa funzione dovrebbe essere assegnato solo un numero pari di argomenti.

  1. C'è un modo per informare il compilatore di byte di questa restrizione?
  2. In caso contrario, è possibile con una macro anziché una funzione?

"... quando vede una funzione invocata con un numero errato di argomenti"?
itsjeyd

Risposte:


13

EDIT : un modo migliore per farlo negli ultimi Emacs consiste nel definire una macro del compilatore per verificare il numero di argomenti. La mia risposta originale usando una normale macro è conservata di seguito, ma una macro del compilatore è superiore perché non impedisce di passare la funzione funcallao applyin fase di esecuzione.

Nelle versioni recenti di Emacs, puoi farlo definendo una macro del compilatore per la tua funzione che controlla il numero di argomenti e produce un avviso (o addirittura un errore) se non corrisponde. L'unica sottigliezza è che la macro del compilatore deve restituire il modulo di chiamata della funzione originale invariato per la valutazione o la compilazione. Questo viene fatto usando un &wholeargomento e restituendo il suo valore. Questo potrebbe essere realizzato in questo modo:

(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

Si noti che funcalle applyora possono essere utilizzati, ma ignorano il controllo degli argomenti dalla macro del compilatore. Nonostante il loro nome, le macro compilatore sembrano anche essere ampliato nel corso del 'interpretato' di valutazione via C-xC-e, M-xeval-buffer, in modo da otterrete errori sulla valutazione e sulla compilazione di questo esempio.


Segue la risposta originale:

Ecco come è possibile implementare il suggerimento di Jordon di "utilizzare una macro che fornirà avvisi al momento dell'espansione". Risulta molto semplice:

(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)

Il tentativo di compilare quanto sopra in un file fallirà (non .elcviene prodotto alcun file), con un bel messaggio di errore cliccabile nel registro di compilazione, che afferma:

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

È inoltre possibile sostituire (error …)con (byte-compile-warn …)per produrre un avviso anziché un errore, consentendo la compilazione per continuare. (Grazie a Jordon per averlo sottolineato nei commenti).

Poiché le macro vengono espanse al momento della compilazione, non vi sono penalità di runtime associate a questo controllo. Naturalmente, non puoi impedire ad altre persone di chiamare my-caller--functiondirettamente, ma puoi almeno pubblicizzarlo come una funzione "privata" usando la convenzione a doppio trattino.

Uno svantaggio notevole dell'utilizzo di una macro per questo scopo è che my-callernon è più una funzione di prima classe: non è possibile passarla a funcallo applyin fase di esecuzione (o almeno non farà ciò che ci si aspetta). A tale proposito, questa soluzione non è altrettanto efficace della possibilità di dichiarare semplicemente un avviso del compilatore per una funzione reale. Ovviamente, l'utilizzo applyrenderebbe impossibile controllare il numero di argomenti passati alla funzione in fase di compilazione, quindi forse questo è un compromesso accettabile.


2
Gli avvisi di compilazione vengono creati conbyte-compile-warn
Jordon Biondo,

Ora mi chiedo se questo potrebbe essere realizzato in modo più efficace definendo una macro di compilatore per la funzione. Ciò eliminerebbe lo svantaggio di non essere applyo funcalldel wrapper macro. Lo proverò e modificherò la mia risposta se funziona.
Jon O.

11

Sì, puoi usare byte-defop-compilerper specificare effettivamente una funzione che compila la tua funzione, byte-defop-compilerha alcune peculiarità integrate per aiutarti a specificare che le tue funzioni dovrebbero produrre avvisi basati sull'avere un numero di argomenti.

Documentazione

Aggiungi un modulo compilatore per FUNCTION. Se la funzione è un simbolo, la variabile "byte-SYMBOL" deve nominare il codice operativo da utilizzare. Se la funzione è un elenco, il primo elemento è la funzione e il secondo elemento è il simbolo bytecode. Il secondo elemento può essere nullo, il che significa che non esiste un codice operativo. COMPILE-HANDLER è la funzione da utilizzare per compilare questo byte-op o può essere l'abbreviazione 0, 1, 2, 3, 0-1 o 1-2. Se è zero, il gestore è "byte-compile-SYMBOL.


uso

Nel tuo caso specifico potresti usare una delle abbreviazioni per definire che alla tua funzione dovrebbero essere dati due argomenti.

(byte-defop-compiler my-caller 2)

Ora la tua funzione darà avvertimenti quando compilata con altro che 2 argomenti.

Se si desidera fornire avvisi più specifici e scrivere le proprie funzioni del compilatore. Guarda byte-compile-one-arge altre funzioni simili in bytecomp.el per riferimento.

Nota che non stai solo specificando alcune funzioni per gestire la validazione, ma in realtà anche la compilazione. Anche in questo caso le funzioni di compilazione in bytecomp.el ti forniranno un buon riferimento.


Percorsi più sicuri

Questo non è qualcosa che ho visto documentato o discusso online, ma nel complesso direi che è sconsigliato un percorso da percorrere. Il percorso corretto (IMO) sarebbe quello di scrivere i tuoi defuns con firme descrittive o utilizzare una macro che fornirà avvisi al momento dell'espansione, controllando la lunghezza dei tuoi argomenti e usando byte-compile-warno errorper mostrare errori. Potrebbe anche essere utile eval-when-compilefare un controllo degli errori.

Dovrai anche definire la tua funzione prima che venga mai utilizzata e la chiamata a byte-defop-compilerdovrà essere prima che il compilatore arrivi alle chiamate effettive della tua funzione.

Ancora una volta, sembra non essere davvero documentato o consigliato da quello che ho visto (potrebbe essere sbagliato) ma immagino che il modello da seguire qui sarebbe quello di specificare una sorta di file di intestazione per il tuo pacchetto che è pieno di un mucchio di defuns vuoti e chiama a byte-defop-compiler. Questo sarebbe fondamentalmente un pacchetto richiesto prima che il tuo pacchetto reale possa essere compilato.

Opinione: Basandomi su ciò che so, che non è molto, perché ho appena imparato tutto ciò, ti consiglierei di non fare mai nulla di tutto ciò. mai


1
Correlati: c'è bytecomp-semplifica che insegna al compilatore di byte ulteriori avvertenze.
Wilfred Hughes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.