2017-08-14 5 views
3

Une utilisation obligatoire de eval-when consiste à s'assurer que les fonctions dont dépend une macro sont disponibles au moment de la compilation et de l'utilisation de la macro. Cependant, je ne peux pas penser à un exemple qui démontrerait les conséquences de ne pas utiliser eval-when.En Common Lisp, quand avez-vous besoin d'utiliser eval-when, et comment le savez-vous?

(defpackage :eval-when 
    (:use :cl)) 

(in-package :eval-when) 

(defun util-fun (x) (* x x)) 

(defmacro needs-help (x) `(let ((a (util-fun ,x))) a)) 

;; use it in the same file 

(defun use-the-macro (x) (needs-help x)) 

(use-the-macro 5) 

Si je comprends bien, le (defun util-fun ...) doit être enveloppé avec eval-when.

EDIT:Comme vous le verrez dans la réponse, il y a un problème avec cet exemple: il n'appelle pas réellement UTIL-FUN au moment de la compilation. Ceci explique pourquoi aucune erreur n'est donnée, car ce n'est pas une erreur. Mais la question est toujours valable en ce qu'elle met en évidence la confusion d'un nouvel utilisateur.

Cependant, à partir du REPL, aucune erreur ou un avertissement est émis lors de la compilation, la charge ou l'utilisation (SBCL 1.3.20):

; SLIME 2.19 
CL-USER> (uiop:getcwd) 
#P"/home/anticrisis/dev/common-lisp/eval-when/" 
CL-USER> (compile-file "eval-when.lisp") 
; compiling file "/home/anticrisis/dev/common-lisp/eval-when/eval-when.lisp" (written 14 AUG 2017 11:30:49 AM): 
; compiling (DEFPACKAGE :EVAL-WHEN ...) 
; compiling (IN-PACKAGE :EVAL-WHEN) 
; compiling (DEFUN UTIL-FUN ...) 
; compiling (DEFMACRO NEEDS-HELP ...) 
; compiling (DEFUN USE-THE-MACRO ...) 
; compiling (USE-THE-MACRO 5) 

; /home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl written 
; compilation finished in 0:00:00.009 
#P"/home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl" 
NIL 
NIL 
CL-USER> (in-package :eval-when) 
#<PACKAGE "EVAL-WHEN"> 
EVAL-WHEN> (use-the-macro 3) 
; Evaluation aborted on #<UNDEFINED-FUNCTION USE-THE-MACRO {10035E1103}>. 
EVAL-WHEN> (needs-help 4) 
; Evaluation aborted on #<UNDEFINED-FUNCTION UTIL-FUN {100387FE33}>. 
EVAL-WHEN> (load "eval-when.lisp") 
T 
EVAL-WHEN> (use-the-macro 3) 
9 
EVAL-WHEN> (needs-help 4) 
16 
EVAL-WHEN> 

Notez que normalement j'utilise Cc Ck eval et charger un fichier à la réplique, mais ici, j'utilise les commandes compile-file et load pour démontrer qu'aucune erreur ne se produit. (Je reçois une erreur lorsque je tente d'utiliser les fonctions après leur compilation, mais avant qu'ils ne soient chargés, mais qui surviendrait avec un code à vide.)

Il y a des questions et des commentaires antérieurs qui se rapportent à ceci:

  • Ce previous StackOverflow answer semble dire très clairement que toute fonction qui est utilisée par une macro doit être entouré par la forme eval-when, ou chargé dans un fichier séparé.

  • Ce commentaire de coredump est aussi très clair:

    Lorsque la macro est étendue, toute fonction que les appels macro doivent être définis . Si vous avez une unité de compilation qui définit une macro, laquelle appelle des fonctions, mais que vous n'utilisez pas réellement la macro dans la même unité de compilation , vous n'avez pas besoin d'une évaluation. Si toutefois vous définissez un aux. fonction, une macro et que vous voulez utiliser votre macro dès que vous le définissez, alors l'implémentation pourrait se plaindre que l'aux. fonction est inconnue - coredump

Étant donné que, pourquoi mon exemple ne génère pas une erreur? Mon exemple va-t-il échouer sous d'autres scénarios? Un exemple d'erreur de compilation, de temps de chargement ou d'exécution générée en cas d'échec de l'utilisation correcte de eval-when serait utile à ma compréhension.

Nous vous remercions pour votre patience!

+1

Vous voudrez peut-être consulter cette question https://stackoverflow.com/questions/10674650/eval-when-uses. Vous pouvez également lire la section eval-when dans PCL http://www.gigamonkeys.com/book/the-special-operators.html –

+0

@DavidHodge Merci pour la référence; J'ai élargi ma question pour être plus précis. – anticrisis

+0

L'autre réponse que vous avez mentionnée parle de la compilation d'un fichier. Vous mentionnez un REPL. Ce sont deux choses différentes. S'il vous plaît également poster une question spécifique, mieux avec le code. –

Répondre

4

Rappelez-vous

EVAL-QUAND est là pour dire au compilateur de fichier si elle doit exécuter du code à la compilation (qui ne sont généralement pas pour les définitions de fonction) et si elle doit organiser le code compilé dans le fichier compilé à exécuter au moment du chargement. Cela ne fonctionne que pour les formulaires de niveau supérieur.Common Lisp exécute le compilateur de fichiers (souvenez-vous que nous parlons de compiler des fichiers, pas d'exécution dans un REPL) dans un environnement Lisp complet et peut exécuter du code arbitraire à la compilation (par exemple dans les outils de l'environnement de développement, générer du code, pour optimiser le code, etc.). Si le compilateur de fichiers veut exécuter du code, les définitions doivent être connues du compilateur de fichier.

Rappelez-vous également que pendant la macro-expansion, le code de la macro est exécuté pour générer le code étendu. Toutes les fonctions et macros que la macro lui-même appelle pour calculer le code, doivent être disponibles au moment de la compilation. Ce qui n'a pas besoin d'être disponible au moment de la compilation, c'est le code auquel la macro-forme se développe.

Ceci est parfois une source de confusion, mais il peut être appris et ensuite l'utiliser n'est pas trop difficile. Mais la partie confuse ici est que le compilateur de fichier lui-même est programmable et peut exécuter du code Lisp au moment de la compilation. Nous devons donc comprendre le concept selon lequel le code peut s'exécuter dans différentes situations: dans un REPL, au moment du chargement, au moment de la compilation, pendant la macro expansion, à l'exécution, etc.

Rappelez-vous également que lorsque vous compilez un fichier, vous devez alors charger le fichier si le compilateur doit en appeler des parties plus tard. Si une fonction est simplement compilée, le compilateur de fichier pas stocke le code dans l'environnement de compilation et également après la fin de la compilation du fichier. Si vous avez besoin du code à exécuter, vous devez charger le code compilé -> ou utiliser EVAL-WHEN -> voir ci-dessous.

Votre code

Votre code ne appel la fonction util-fun au moment de la compilation . Donc, la fonction et non doit être disponible dans l'environnement de compilation.

Un exemple

Un autre exemple, où la fonction est en fait appelée, voir ci-dessous. C'est le code dans un fichier Lisp, à compiler par compile-file.

(defun run-at-compile-time() 
    (print 'I-am-called-at-compile-time)) 

(defmacro foo() 
    (run-at-compile-time)    ; this function is called for its 
            ; side-effect: it prints something 
    '(print 'I-am-called-at-runtime)) ; this code is returned 

(foo)  ; we use the macro in our code, the compiler needs to expand it. 

Ainsi lors de l'expansion macro la macro foo aime appeler la fonction run-at-compile-time, qui est défini dans le même fichier. Comme il n'est pas disponible dans l'environnement de compilation, il s'agit d'une erreur. Le compilateur de fichiers génère uniquement le code pour la fonction à stocker sur le disque, de sorte que lorsque le fichier compilé est chargé, la fonction est définie. Mais il ne définit pas la fonction dans le Lisp exécutant le compilateur -> le compilateur de fichier ne peut pas l'appeler.

Initiez EVAL-QUAND

Pour dire au compilateur de laisser aussi l'environnement de compilation connaissent, vous avez besoin de l'envelopper dans un EVAL-QUAND et ajoutez la: la situation de compilation de premier niveau. Ensuite, lorsque le compilateur de fichier voit la fonction à toplevel, il exécute la macro de définition.

(eval-when 

    (:compile-toplevel ; this top-level form will be executed by the file compiler 

    :load-toplevel  ; this top-level form will be executed at load-time 
         ; of the compiled file 

    :execute)   ; executed whenever else 

    (defun run-at-compile-time() 
     (print 'I-am-called-at-compile-time)) 

) 

Vous pouvez également mentionner une ou deux des situations. Par exemple, le formulaire peut être exécuté lorsque le compilateur de fichier le voit au niveau supérieur et seulement ensuite. Il ne serait pas exécuté au moment du chargement ou dans d'autres situations.

+0

Superbe explication, merci, mais il y a un autre point que je me demande si vous pourriez clarifier: le fait qu'en pratique, si vos définitions de macros sont dans un fichier distinct des fichiers qui les utilisent réellement, alors ce problème n'est jamais visible, et eval-when n'est pas nécessaire.Cela, je crois, a contribué à ma propre confusion. – anticrisis

+1

@anticrisis: alors vous devez vous assurer que les fichiers, le code (source ou compilé) dépend o n, sont chargés (!) dans le Lisp de compilation avant de compiler les fichiers dépendants. Juste les compiler n'est pas suffisant. –