2010-02-10 16 views
6

Ho scritto alcuni semplici casi di test per uno dei miei incarichi e ho creato un po 'di test con le macro. Ho run-test e run-test-section e così via. Mi piacerebbe che run-test-section prendesse un numero di parametri che sono invocazioni run-test e conta il numero di PASS e FAIL.Come scrivere una chiamata macro ricorsiva su un parametro & REST in Lisp?

run-test restituisce T su PASS e NIL su FAIL.

Quello che sto cercando di fare in questo momento è scrivere una macro che prende un parametro &REST, e invoca ciascuno degli elementi di questo elenco, infine, restituendo il numero dei veri valori.

Questo è quello che ho attualmente:

(defmacro count-true (&rest forms) 
`(cond 
    ((null ,forms) 
     0) 
    ((car ,forms) 
     (1+ (count-true (cdr ,forms)))) 
    (T 
     (count-true (cdr ,forms))))) 

Tuttavia, questo mette il mio REPL in un ciclo infinito. Qualcuno potrebbe essere in grado di indicare come posso manipolare in modo più efficace gli argomenti. Questa è anche una buona idea? C'è un approccio migliore?

edit:

Come si nota nelle risposte, una macro non è necessaria in questo caso. L'utilizzo del numero di serie COUNT sarà sufficiente. Vi sono tuttavia informazioni utili nelle risposte sulle chiamate macro ricorsive.

risposta

5

Al tempo di espansione macro, cdr non viene valutato. Così (count-true t t nil) colpisce un'espansione infinita come questo:

(count-true t t nil) 
=> 
(1+ (count-true (cdr (t t t nil)))) 
=> 
(1+ (1+ (count-true (cdr (cdr (t t t nil)))))) 
=> 
(1+ (1+ (1+ (count-true (cdr (cdr (cdr (t t t nil)))))))) 
=> ... 

Beh, in realtà questo avviene per entrambi i rami ricorsive in una sola volta. Quindi esplode ancora più veloce dell'esempio.

Un'idea migliore?

  1. Provare prima a scrivere la stessa cosa di una funzione. Scopri dove devi mettere i lambda per ritardare la valutazione. Quindi astratta la funzione in una macro in modo da poter lasciare fuori i lambda.

    Il punto di scrivere prima una funzione, a proposito, è che a volte si scopre che una funzione è abbastanza buona. Scrivere una macro dove una funzione dovrebbe essere un errore.

  2. In genere, quando si scrivono macro in Common Lisp, si inizia con loop anziché con ricorsione. Le macro ricorsive sono difficili (e di solito sbagliano :).

Edit:

Ecco un esempio più corretto (ma molto più lungo):

(count-true t nil) => 
(cond 
    ((null '(t nil)) 0) 
    ((car '(t nil)) (1+ (count-true (cdr '(t nil))))) 
    (T (count-true (cdr '(t nil))))) 
=> 
(cond 
    ((null '(t nil)) 0) 
    ((car '(t nil)) (1+ (1+ (count-true (cdr (cdr '(t nil))))))) 
    (T (count-true (cdr (cdr '(t nil)))))) 
=> 
(cond 
    ((null '(t nil)) 0) 
    ((car '(t nil)) (1+ (1+ (1+ (count-true (cdr (cdr (cdr '(t nil))))))))) 
    (T (count-true (cdr (cdr (cdr '(t nil))))))) 
=> 
(cond 
    ((null '(t nil)) 0) 
    ((car '(t nil)) (1+ (1+ (1+ (1+ (count-true (cdr (cdr (cdr (cdr '(t nil))))))))))) 
    (T (count-true (cdr (cdr (cdr (cdr '(t nil)))))))) 
+0

Grazie per la spiegazione. Vedo ora la mia errata comprensione dell'espansione macro. buon consiglio sull'uso di 'LOOP' invece. perché ho capito come usare solo una funzione per questo compito. –

3

Il problema è che la macro si espande in una forma infinita. Il sistema cercherà di espanderlo tutto durante la fase di espansione della macro e quindi dovrà esaurire la memoria.

Si noti che il test per la fine dell'elenco di moduli non viene mai valutato ma espresso come codice. Dovrebbe essere al di fuori dell'espressione del backtick. E come l'altra persona ha spiegato, anche i moduli (cdr) devono essere valutati al momento dell'espansione della macro, non lasciati come codice da compilare.

I.e. qualcosa di simile (non testata):

(defmacro count-true (&rest forms) 
    (if forms 
     `(if (car ',forms) 
      (1+ (count-true ,@(cdr forms))) 
     (count-true ,@(cdr forms))) 
    0)) 
4

Dimenticate le macro ricorsive. Sono un dolore e davvero solo per utenti Lisp avanzati.

Un semplice, versione non ricorsiva:

(defmacro count-true (&rest forms) 
    `(+ 
    ,@(loop for form in forms 
      collect `(if ,form 1 0)))) 

CL-USER 111 > (macroexpand '(count-true (plusp 3) (zerop 2))) 
(+ (IF (PLUSP 3) 1 0) (IF (ZEROP 2) 1 0)) 

Bene, qui è una macro ricorsiva:

(defmacro count-true (&rest forms) 
    (if forms 
     `(+ (if ,(first forms) 1 0) 
      (count-true ,@(rest forms))) 
    0)) 
+0

Perché non 'contare, form' invece di' + 'e' collect'? – Svante

+0

Poiché LOOP non conta i risultati. Genera codice. È andato dopo l'espansione. –

+0

grazie per la risposta. è diretto e risponde alla domanda di lavorare con i parametri di riposo in una macro ricorsiva. –

2

io credo che tu sei sotto due false impressioni riguardo a ciò che le macro sono.

Le macro sono scritte per espansione, funzioni per l'esecuzione. Se scrivi una macro ricorsiva, si espanderà ricorsivamente, senza eseguire nulla del codice che produce. Una macro è per niente qualcosa come una funzione in linea!

Quando si scrive una macro, è possibile espandere la funzione per le chiamate. Quando scrivi una macro, fai non in "macro land" dove le funzioni non saranno disponibili. Non ha alcun senso scrivere count-true come una macro.

+1

l'idea di una macro ricorsiva è in genere che si espande in codice, che utilizza nuovamente quella macro, ma su argomenti più semplici/più piccoli. L'espansione della macro si interrompe quando la macro viene utilizzata nella sua forma più semplice. Quindi la ricorsione viene eseguita tramite il meccanismo di espansione macro ... –

+0

grazie per avere la testa dritta. ho capito come ottenere ciò che stavo cercando usando solo una funzione. Ho avuto l'impressione di dover usare una macro per il modo in cui pensavo di interagire con le espressioni valutate. ora vedo come mi sono sbagliato. –

0

Come altri hanno già detto, evitare le macro ricorsive. Se siete disposti a fare questo in una funzione, è possibile utilizzare apply:

(defun count-true (&rest forms) 
    (cond 
    ((null forms) 0) 
    (t (+ 1 (apply #'count-true (cdr forms)))))) 
1

Questa è una simpatica applicazione per un facile ricorsione macro:

(defmacro count-true (&rest forms) 
    (cond 
    ((null forms) 0) 
    ((endp (rest forms)) `(if ,(first forms) 1 0)) 
    (t `(+ (count-true ,(first forms)) (count-true ,@(rest forms)))))) 

Test:

[2]> (count-true) 
0 
[3]> (count-true nil) 
0 
[4]> (count-true t) 
1 
[5]> (count-true nil t) 
1 
[6]> (count-true t nil) 
1 
[7]> (count-true t t) 
2 
[8]> (count-true nil nil) 
0 
[9]> (macroexpand '(count-true)) 
0 ; 
T 
[10]> (macroexpand '(count-true x)) 
(IF X 1 0) ; 
T 
[11]> (macroexpand '(count-true x y)) 
(+ (COUNT-TRUE X) (COUNT-TRUE Y)) ; 
T 
[12]> (macroexpand '(count-true x y z)) 
(+ (COUNT-TRUE X) (COUNT-TRUE Y Z)) ; 
T 

La macro deve ragionare sull'ingresso sintassi e generare il codice che esegue il conteggio; non devi essere confuso tra la generazione del codice e la valutazione.

Stai andando storto destra fuori del blocco, quando si sta facendo questo:

`(cond ((null ,forms ...) ...) 

si sta spingendo il calcolo meta-sintattica (quante forme cosa abbiamo nella sintassi?) Nel il modello di codice generato deve essere valutato in fase di esecuzione. Hai i pezzi giusti in questo caso base, ma sono messi in scena male. Nella mia soluzione, ho la cond solo nel corpo macro in sé, non in un backquote:

(cond ((null forms) ...) ...) 

In sostanza:

(cond (<if the syntax is like this> <generate this>) 
     (<if the syntax is like that> <generate that>) 
     ...) 

Se non sai cosa fare, scrivere il codice che vuoi che la macro scriva.Per esempio:

;; I want this semantics: 
(if (blah) 1 0) ;; count 1 if (blah) is true, else 0 

;; But I want it with this syntax: 
(count-true (blah)) 

Okay, quindi per questo caso esatto, ci avrebbe scritto:

(defmacro count-true (single-form) 
    `(if ,single-form 1 0)) 

Fatto! Ora supponiamo di voler supportare (count-true) senza alcun modulo.

Wanted Syntax     Translation 

(count-true)     0 
(count-true x)     (if x 1 0) 

Quando c'è un modulo, l'espansione if rimane, ma quando non ci sono forme, vogliamo solo uno zero costante. Facile, rendere l'argomento opzionale:

(defmacro count-true (&optional (single-form nil have-single-form)) 
    (if have-single-form 
    `(if ,single-form 1 0) ;; same as before 
     0))     ;; otherwise zero 

Infine, si estendono a forma di N-ary:

Wanted Syntax     Translation 

(count-true)     0 
(count-true x)     (if x 1 0) 
(count-true x y)    (+ (if x 1 0) (if y 1 0)) 
            ^^^^^^^^^^ ^^^^^^^^^^ 

Ma! ora notiamo che i termini sottolineati corrispondono all'output del singolo caso.

(count-true x y)    (+ (count-true x) (count-true y)) 

che generalizza

(count-true x y z ...)   (+ (count-true x) (count-true y z ...)) 

che corrisponde in modo diretto al modello di generazione di codice con car/cdr ricorsione:

`(+ (count-true ,CAR) (count-true ,*CDR)) 
Problemi correlati