2017-09-29 7 views
1

Je suis en train d'apprendre le lisp avec le livre de Graham "ANSI Common Lisp" et comme exercice j'écris des calculs de calendrier basés sur le jour de Julian. Comme vous le savez, le dimanche de Pâques change d'année en année et il y a environ 10 autres jours spéciaux dont la date réelle dépend de la date du dimanche de Pâques.Utilisation d'une macro pour définir les fonctions de calcul des 10 dates liées à Pâques

Je veux définir une fonction pour chacun de ces jours, tous suivant le schéma suivant:

(defun carnaval (year) 
    "Carnaval Monday of YEAR. 

This is 48 days before Easter Sunday." 
    (- (easter year) 48)) 

Au lieu de répéter 10 fois des déclarations similaires, il semble approprié d'utiliser une macro:

(defmacro %defeasterday (function-name screen-name offset) 
    `(defun ,function-name (year) 
    ,(format nil "~A of YEAR.~%~%This is ~A day~:p ~A Easter Sunday" 
       screen-name 
       (abs offset) 
       (if (< 0 offset) "after" "before")) 
    (+ (easter year) ,offset))) 

(Le début % marque mon intention de ne pas exporter la macro dans le package où elle est définie.)

La macro peut être utilisée pour d éfinir une fonction pour chacun des jours dont la date est basée sur la date de l'est Dimanche:

(%defeasterday carnaval "Carnaval Monday" -48) 
(%defeasterday mardi-gras "Mardi gras"  -47) 
(%defeasterday ash  "Ash Wednesday" -46) 
… 

Maintenant, pour le bien de l'exercice, je voudrais emballer toutes les données sur une liste et utiliser la %defeasterday macro ses articles. Ma tentative a été

(mapC#'(lambda (args) (apply #'%defeasterday args)) 
     '((carneval "Carneval Monday" -48) 
     (mardi-gras "Mardi gras"  -47) 
     (ash  "Ash Wednesday" -46))) 

Cela échoue avec

Execution of a form compiled with errors. 
Form: 
    #'%DEFEASTERDAY 
Compile-time error: 
    The :macro name %DEFEASTERDAY was found as the argument to FUNCTION. 
    [Condition of type SB-INT:COMPILED-PROGRAM-ERROR] 

qui me apprend qu'une macro est pas « juste un code de mappage de fonction code » car apply est pointilleux sur les cours d'exécution.

Comment puis-je utiliser la macro %defeasterday ci-dessus pour itérer sur une liste?

(Si vous avez besoin d'une fonction ad hoc easter aux fins de test, s'il vous plaît (defun easter() 2457860) qui donne la réponse attendue pour 2017.)

Répondre

5

L'application d'une macro ne fonctionne pas

Vous pouvez » t appliquer une macro:

Mais vous pouvez évaluer un formulaire:

(eval (let ((args '(foo bar))) 
     `(defsomething ,@args))) 

ou

(let ((args '(foo bar))) 
    (eval `(defsomething ,@args))) 

Voir aussi la fonction COMPILE, si vous voulez vous assurer que le code est compilé.

En utilisant une définition macro

La bonne façon d'utiliser une définition (!) Macro est la suivante:

(%defeasterdays 
    (carnaval "Carnaval Monday" -48) 
    (mardi-gras "Mardi gras"  -47) 
    (ash  "Ash Wednesday" -46)) 

La macro %defeasterdays devrait alors développer ci-dessus dans:

(progn 
(%defeasterday carnaval "Carnaval Monday" -48) 
(%defeasterday mardi-gras "Mardi gras"  -47) 
(%defeasterday ash  "Ash Wednesday" -46)) 

DEFUN est un macro-niveau. Une fois que vous voulez habituellement le garder de cette façon. Si vous utilisez EVAL d'un formulaire DEFUN, il n'est pas au niveau supérieur pour un compilateur de fichiers. Ainsi, vous devez effectuer la transformation dans une macro, de telle sorte que les formes définissantes soient toujours au niveau le plus élevé. PROGN sous-formulaires sont toujours au niveau supérieur pour un compilateur de fichiers.

Faire le compilateur de fichier heureux

Vous pouvez utiliser le compilateur de fichier pour compiler le code suivant:

; we need the file compiler to know the value of *DAYS* 
; thus the eval-when. By default `DEFVAR` would not have 
; been executed 
(eval-when (:compile-toplevel :load-toplevel :execute) 
    (defvar *days* 
    '((carnaval "Carnaval Monday" -48) 
     (mardi-gras "Mardi gras"  -47) 
     (ash  "Ash Wednesday" -46)))) 

; a file compiler sees the following macro 
: and its definition is automatically available at compile time 
(defmacro %defeasterday (function-name screen-name offset) 
    `(defun ,function-name (year) 
    ,(format nil "~A of YEAR.~%~%This is ~A day~:p ~A Easter Sunday" 
       screen-name 
       (abs offset) 
       (if (< 0 offset) "after" "before")) 
    (+ (easter year) ,offset))) 

; same here, the compiler learns about the next macro 
(defmacro %defeasterdays (list) 
    `(progn ,@(loop for item in (symbol-value list) 
        collect `(%defeasterday ,@item)))) 

; now the file compiler sees the following form. 
; it will be macro expanded. The macros are known. 
; one of the macros now needs at expansion time the value 
; of *DAYS*. Since we have made *DAYS* known to the compiler, 
; this will work. 
(%defeasterdays *days*) 

L'avantage principal est que le compilateur de fichier voir la définition de tous vos produit fonctions au moment de la compilation. Il sera capable de générer du code efficace pour les fonctions et il pourra également générer du code efficace pour les formulaires appelant ces fonctions.

Vous pouvez également charger ce fichier, mais cela dépend de l'implémentation, que le code soit compilé ou que vous vous retrouviez avec des fonctions interprétées.

+0

Merci beaucoup pour votre réponse, c'est une très belle proposition. Je laisse la question ouverte pendant quelques jours, au cas où quelqu'un voudrait aussi écrire une réponse alternative. –