2012-02-08 12 views
5

Supponiamo che io sono una chiusura giardino-varietà come questo scarno campione:Come può una chiusura riferirsi a se stessa?

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

Supponiamo che io (funcall) un'istanza che la chiusura per tre volte, e nel mezzo della terza esecuzione, questa chiusura vuole salvare se stessa da qualche parte (in un tavolo hash, per esempio). Quindi non faccio (funcall) questa istanza per un po '. Quindi richiamo questa istanza dalla tabella hash e (funcall) nuovamente, ottenendo il valore restituito di 4.

In che modo la funzione nella chiusura si riferisce a se stessa, quindi può salvarsi in quella tabella hash?

MODIFICA 1: Ecco un esempio più dettagliato. Raggiungere l'obiettivo passando la chiusura a se stesso come parametro. Ma mi piacerebbe che la chiusura facesse tutto questo a se stessa senza essere auto-parametrizzata.

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: Sì, so che posso utilizzare una macro per scivolare il nome della funzione come primo parametro, e quindi utilizzare tale macro invece di (funcall), ma mi piace ancora di sapere come lasciare che una chiusura faccia riferimento alla sua stessa istanza.

EDIT 3: In risposta al gentile suggerimento di SK-logic, ho fatto quanto segue, ma non fa quello che voglio. Spinge tre nuove chiusure in pila, non tre riferimenti alla stessa chiusura. Guarda come quando li faccio fuori dallo stack, i valori delle chiamate sono 1, 1 e 1 invece di 6, 7 e 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 

MODIFICA 4: il suggerimento di Jon O ha raggiunto il punto esatto. Ecco il codice e l'output:

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: il suggerimento di Miron colpisce anche il marchio, e in realtà rende il codice un po 'più leggibile:

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

Suppongo che per "chiusura" intendi "funzione anonima". In ogni caso, non vedo perché non si possa semplicemente dargli un nome? – delnan

+0

Non voglio una nuova istanza della chiusura. Voglio il vecchio, con continue modifiche alle variabili che lo racchiudono. In realtà, potrebbero esserci diversi casi, ognuno dei quali dovrebbe essere salato via da qualche parte. Tutto questo può essere fatto dandogli un nome? Quale sarebbe la sintassi? –

+0

Non parlo molto bene, ma suppongo che qualcosa come le "espressioni di funzione autoeseguibili" comuni in JavaScript facciano il trucco. – delnan

risposta

4

Che dire di alambda (anche in On Lisp)?

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

Ho usato questo in EDIT 5 della domanda. In realtà rende il codice ancora più leggibile. Grazie! –

+2

Bene, è l'implementazione più generale per l'idea delle 'etichette'. Si potrebbe anche trovare utile 'blambda', dove si arriva anche al nome' self'. Vedi [Alexandria] (http://common-lisp.net/project/alexandria/draft/alexandria.html) per altri esempi di questo (cerca 'if-let'). –

+2

Esempio 'blambda':' (defmacro blambda (nome-arg args e corpo) \ '(etichette ((, nome-fn, args, corpo @)) # ', nome-fn))', usato in ' (funcall (blambda fact (x) (if (= 0 x) 1 (* x (fact (1- x))))) 5) ' –

7

Non credo che avete bisogno di andare come per quanto riguarda la definizione del combinatore Y per te stesso per fare questo; il modulo integrato labels creerà i collegamenti autoreferenziali necessari. Secondo il HyperSpec:

"labels è equivalente a flet tranne che la portata dei nomi delle funzioni definite per etichette comprende la funzione stesse definizioni come pure il corpo."

Ecco ad esempio la chiusura giocattolo preferito di tutti, mostrando come l'localmente definito f chiude sulla propria vincolante:

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

Ciò restituisce una chiusura che restituisce due valori: il nuovo valore del contatore, e la sua valore della funzione. Esempio di utilizzo:

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) 

Dovrebbe essere semplice estenderlo per archiviare la chiusura in una struttura dati anziché restituirla.

+0

Perfetto! (Vedi i risultati nella domanda modificata). Grazie! –

+0

Sono contento che sia stato utile! –

+1

@Bill: correlati (ma non duplicati): http://stackoverflow.com/q/7936024/13, che spiega perché è richiesto 'labels' o' letrec' o un combinatore Y o simile. (Divulgazione: ho scritto la risposta accettata.) –

Problemi correlati