2012-02-08 1 views
5

Supposons que j'ai une fermeture variété de jardin comme cet échantillon bare-bones:Comment une fermeture peut-elle se référer à elle-même?

(let ((alpha 0) #| etc. |#) 
    (lambda() 
    (incf alpha) 
    #| more code here |# 
    alpha)) 

Supposons que je (funcall) une instance de cette fermeture à trois reprises, et au milieu de la troisième exécution, cette fermeture veut se sauver quelque part (dans une table de hachage, par exemple). Ensuite, je ne sais pas (funcall) cette instance pendant un moment. Ensuite, je récupère cette instance de la table de hachage et (funcall) encore, obtenant la valeur retournée de 4.

Comment la fonction dans la fermeture se réfère à elle-même, de sorte qu'elle peut se sauver dans cette table de hachage?

EDIT 1: Voici un exemple plus détaillé. J'accomplis le but en passant la fermeture à elle-même en tant que paramètre. Mais j'aimerais que la fermeture fasse tout ça sans être auto-paramétrée.

1 (defparameter *listeriosis* nil) 
2 (defparameter *a* 
3 (lambda() 
4  (let ((count 0)) 
5  (lambda (param1 param2 param3 self) 
6   (incf count) 
7   (when (= 3 count) 
8   (push self *listeriosis*) 
9   (push self *listeriosis*) 
10   (push self *listeriosis*)) 
11   count)))) 
12 (let ((bee (funcall *a*))) 
13 (princ (funcall bee 1 2 3 bee)) (terpri) 
14 (princ (funcall bee 1 2 3 bee)) (terpri) 
15 (princ (funcall bee 1 2 3 bee)) (terpri) 
16 (princ (funcall bee 1 2 3 bee)) (terpri) 
17 (princ (funcall bee 1 2 3 bee)) (terpri)) 
18 (princ "///") (terpri) 
19 (princ (funcall (pop *listeriosis*) 1 2 3 nil)) (terpri) 
20 (princ (funcall (pop *listeriosis*) 1 2 3 nil)) (terpri) 
21 (princ (funcall (pop *listeriosis*) 1 2 3 nil)) (terpri) 
1 
2 
3 
4 
5 
/// 
6 
7 
8 

EDIT 2: Oui, je sais que je peux utiliser une macro pour glisser le nom de la fonction en tant que premier paramètre, et ensuite utiliser cette macro au lieu de (funcall), mais je voudrais encore savoir comment laisser une fermeture se référer à sa propre instance.

EDIT 3: En réponse à la suggestion aimable de SK-logic, j'ai fait ce qui suit, mais il ne fait pas ce que je veux. Il pousse trois nouvelles fermetures sur la pile, et non trois références à la même fermeture. Voyez-vous comment, lorsque je les retire de la pile, les valeurs des appels sont 1, 1 et 1 au lieu de 6, 7 et 8?

1 (defparameter *listeriosis* nil) 
2 (defun Y (f) 
3 ((lambda (x) (funcall x x)) 
4 (lambda (y) 
5  (funcall f (lambda (&rest args) 
6    (apply (funcall y y) args)))))) 
7 (defparameter *a* 
8 (lambda (self) 
9  (let ((count 0)) 
10  (lambda (param1 param2 param3) 
11   (incf count) 
12   (when (= 3 count) 
13   (push self *listeriosis*) 
14   (push self *listeriosis*) 
15   (push self *listeriosis*)) 
16   count)))) 
17 (let ((bee (Y *a*))) 
18 (princ (funcall bee 1 2 3 #| bee |#)) (terpri) 
19 (princ (funcall bee 1 2 3 #| bee |#)) (terpri) 
20 (princ (funcall bee 1 2 3 #| bee |#)) (terpri) 
21 (princ (funcall bee 1 2 3 #| bee |#)) (terpri) 
22 (princ (funcall bee 1 2 3 #| bee |#)) (terpri)) 
23 (princ "///") (terpri) 
24 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
25 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
26 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
1 
2 
3 
4 
5 
/// 
1 
1 
1 

EDIT 4: La suggestion de Jon O a atteint exactement la cible. Voici le code et la sortie:

1 (defparameter *listeriosis* nil) 
2 (defparameter *a* 
3 (lambda() 
4  (let ((count 0)) 
5  (labels ((self (param1 param2 param3) 
6     (incf count) 
7     (when (= 3 count) 
8     (push (function self) *listeriosis*) 
9     (push (function self) *listeriosis*) 
10     (push (function self) *listeriosis*)) 
11     count)) 
12   (function self))))) 
13 (let ((bee (funcall *a*))) 
14 (princ (funcall bee 1 2 3)) (terpri) 
15 (princ (funcall bee 1 2 3)) (terpri) 
16 (princ (funcall bee 1 2 3)) (terpri) 
17 (princ (funcall bee 1 2 3)) (terpri) 
18 (princ (funcall bee 1 2 3)) (terpri)) 
19 (princ "///") (terpri) 
20 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
21 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
22 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
1 
2 
3 
4 
5 
/// 
6 
7 
8 

EDIT 5: La suggestion de Miron touche également la marque, et fait en fait un peu le code plus lisible:

1 (defmacro alambda (parms &body body) 
2 `(labels ((self ,parms ,@body)) 
3  #'self)) 
4 ; 
5 (defparameter *listeriosis* nil) 
6 (defparameter *a* 
7 (lambda() 
8  (let ((count 0)) 
9  (alambda (param1 param2 param3) 
10   (incf count) 
11   (when (= 3 count) 
12   (push #'self *listeriosis*) 
13   (push #'self *listeriosis*) 
14   (push #'self *listeriosis*)) 
15   count)))) 
16 ; 
17 (let ((bee (funcall *a*))) 
18 (princ (funcall bee 1 2 3)) (terpri) 
19 (princ (funcall bee 1 2 3)) (terpri) 
20 (princ (funcall bee 1 2 3)) (terpri) 
21 (princ (funcall bee 1 2 3)) (terpri) 
22 (princ (funcall bee 1 2 3)) (terpri)) 
23 (princ "///") (terpri) 
24 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
25 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
26 (princ (funcall (pop *listeriosis*) 1 2 3)) (terpri) 
1 
2 
3 
4 
5 
/// 
6 
7 
8 
+0

Je suppose que par "fermeture" vous voulez dire "fonction anonyme". En tout cas, je ne vois pas pourquoi vous ne lui donneriez pas simplement un nom? – delnan

+0

Je ne veux pas une nouvelle instance de la fermeture. Je veux l'ancien, avec des changements continus aux variables englobantes. En fait, il pourrait y avoir plusieurs cas, dont chacun devrait être salé quelque part. Tout cela peut-il être fait en lui donnant un nom? Quelle serait la syntaxe? –

+0

Je ne parle pas très bien, mais je suppose que quelque chose comme les "expressions de fonctions auto-exécutables" communes à JavaScript ferait l'affaire. – delnan

Répondre

4

Qu'en est-il de alambda (aussi dans On Lisp)?

;; Graham's alambda 
(defmacro alambda (parms &body body) 
    `(labels ((self ,parms ,@body)) 
    #'self)) 
+0

J'ai utilisé ceci dans EDIT 5 de la question. Cela rend le code encore plus lisible. Merci! –

+2

Eh bien, c'est l'implémentation la plus générale pour l'idée 'labels'. Vous pouvez également trouver 'blambda' utile, où vous obtenez également nom' self'. Voir [Alexandria] (http://common-lisp.net/project/alexandria/draft/alexandria.html) pour plus d'exemples de ceci (cherchez 'if-let'). –

+2

Exemple 'blambda':' (defmacro blambda (fn-name args & corps du corps) \ '(étiquettes ((, nom-fn, args, @ corps)) # ', fn-name))', utilisé dans ' (funcall (fait blambda (x) (si (= 0 x) 1 (* x (fait (1- x))))) 5) ' –

7

Je ne pense pas que vous devez aller loin de définir le combinateur Y pour vous-même afin de le faire; le formulaire intégré labels créera les liaisons auto-référentielles dont vous avez besoin. Selon le HyperSpec:

« labels équivaut à flet, sauf que la portée des noms de fonctions définies pour les étiquettes comprend la fonction se définitions ainsi que le corps. »

est ici par exemple de fermeture de jouet préféré de tout le monde, montrant comment le f défini localement ferme sur sa propre liaison:

(defun make-counter (n) 
    (labels ((f() (values (incf n) (function f)))) 
    (function f))) 

Cela renvoie une fermeture qui retourne deux valeurs: la nouvelle valeur du compteur, et sa propre valeur de fonction. Exemple d'utilisation:

CL-USER> (setq g (make-counter 5)) 
#<FUNCTION F NIL (BLOCK F (VALUES (INCF N) #'F))> 
CL-USER> (multiple-value-bind (n q) (funcall g) (list n (funcall q))) 
(6 7) 

Il devrait être facile d'étendre cela au stockage de la fermeture dans une structure de données au lieu de la renvoyer.

+0

Parfait! (Voir les résultats dans la question éditée.) Merci! –

+0

Heureux que cela ait été utile! –

+1

@Bill: Relatif (mais pas en double): http://stackoverflow.com/q/7936024/13, ce qui explique pourquoi 'labels' ou' letrec' ou Y combinator ou similaire est requis. (Divulgation: J'ai écrit la réponse acceptée.) –

Questions connexes