2015-07-31 28 views
6

Sto provando a scrivere una macro ELisp per generare più funzioni basate su alcuni dati comuni. Ad esempio, quando voglio calcolare i nomi fn che scrivo qualcosa di simile (sto ignorando igiene per il momento, sto passando un simbolo letterale nella macro in modo da valutazione non dovrebbe avere importanza):Impossibile chiamare le funzioni definite nella macro con i nomi generati dal simbolo di creazione

(cl-defmacro def-fns (sym) 
    "SYM." 
    (let ((s1 (make-symbol (concat (symbol-name sym) "-1"))) 
     (s2 (make-symbol (concat (symbol-name sym) "-2")))) 
    `(progn (defun ,s1() (+ 1 2 3)) 
      (defun ,s2() "six")))) 

che mi aspetto di generare 2 fns quando invocato, chiamato foo-1 e foo-2.

devo quindi in grado di richiamare la macro e FNS modo:

(def-fns foo) 
(foo-1) 
;; => 6 
(foo-2) 
;; -> "six 

Anche la macroexpansion di (def-fns foo) in Emacs suggerisce che questo dovrebbe essere il caso:

(progn 
    (defun foo-1 nil (+ 1 2 3)) 
    (defun foo-2 nil "six")) 

Tuttavia, quando Valuto la definizione def-fns e la invoco lo fa non genera quelle funzioni. Perché è così? Questa tecnica funziona in Common Lisp e in Clojure (che hanno sistemi macro molto simili), quindi perché non in ELisp?

risposta

9

Il tuo codice non funzionerebbe neanche in CL.

Il problema è con make-symbol - crea un nuovo simbolo, in modo che

(eq (make-symbol "A") (make-symbol "A")) 
==> nil 

Ciò significa che la macro crea le funzioni, ma li lega ai simboli che non hai più una maniglia su.

Quando si valuta (foo-1), lettore di Emacs Lisp cerca di trovare la funzione legame del internato simbolo foo-1, non il simbolo fresco uninterned la macro creata.

è necessario utilizzare intern invece: rende il simbolo "generalmente disponibili", per così dire:

(eq (intern "a") (intern "a)) 
==> t 

Quindi, il codice corretto si presenta così:

(defmacro def-fns (sym) 
    "SYM." 
    (let ((s1 (intern (concat (symbol-name sym) "-1"))) 
     (s2 (intern (concat (symbol-name sym) "-2")))) 
    `(progn (defun ,s1() (+ 1 2 3)) 
      (defun ,s2() "six")))) 
(def-fns foo) 
(foo-1) 
==> 6 
(foo-2) 
==> "six" 

Note :

  1. Se si utilizza CL, il simbolo senza indirizzo s sarebbe stato stampato come #:foo-1 e la fonte del tuo problema sarebbe stata ovvia per te.
  2. È eccessivamente raro che si davvero necessario utilizzare make-symbol. Di solito, si desidera utilizzare intern o gensym.
+0

Infatti. È passato un po 'di tempo da quando ho scritto qualsiasi macro di stile CL/Clojure/Elisp ed è un errore facile da realizzare, apparentemente ancora più semplice in Elisp che in CL :) – jjpe