Domanda

Al momento sto lavorando attraverso SICP usando Guile come la mia lingua principale per gli esercizi. Ho trovato uno strano comportamento durante l'implementazione degli esercizi nel capitolo 3.5. Ho riprodotto questo comportamento utilizzando Guile 1.4, Guile 1.8.6 e 1.8.7 Guile su una varietà di piattaforme e sono certo che non è specifico per il mio setup.

Questo codice funziona bene (e calcola e):

  (define y (integral (delay dy) 1 0.001))
  (define dy (stream-map (lambda (x) x) y))
  (stream-ref y 1000)

Il seguente codice dovrebbe dare un risultato identico:

  (define (solve f y0 dt)
    (define y (integral (delay dy) y0 dt))
    (define dy (stream-map f y))
    y)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

Ma produce il messaggio di errore:

standard input:7:14: While evaluating arguments to stream-map in expression (stream-map f y):
standard input:7:14: Unbound variable:
y ABORT: (unbound-variable)

Così, quando incorporato in una definizione di procedura, il (definire y ...) non funziona, mentre fuori della procedura nel contesto globale al REPL funziona benissimo.

Che cosa sto facendo male qui? Posso pubblicare il codice ausiliario (cioè, le definizioni di integrale, stream-map etc.) se necessario, anche. Con l'eccezione del codice dipendente dal sistema di contro-flusso, sono tutti nel libro. La mia propria implementazione di contro-flusso per Guile è il seguente:

(define-macro (cons-stream a b)
  `(cons ,a (delay ,b)))
È stato utile?

Soluzione

La differenza fondamentale tra ciò che accade quando si valutano le definizioni uno ad uno al REPL e quando si inseriscono all'interno solve è che nel primo caso, vengono valutati in sequenza, così la y il (stream-map <some-function> y) espressione si riferisce è già portata, mentre con definizioni interne o letrec, non è ancora disponibile.

Stranamente, MIT Scheme, che ho usato quando si passa attraverso SICP, ha avuto nessun problema del genere allora e ancora ossequi letrec e definisce interni in modo diverso:

;; this is an error
(letrec ((xs '(1 2 3)) (ys (map (lambda (x) (+ x 1)) xs))) ys)

;; this is still an error (and is treated as such by Guile),
;; yet evaluates to (2 3 4) in MIT Scheme
(let () (define xs '(1 2 3)) (define ys (map (lambda (x) (+ x 1)) xs)) ys)

Non sono sicuro circa l'originale "Revised Rapporto sullo schema Lingua algoritmico" o R2RS, ma almeno dal R3RS sulla definisce interne dovevano essere equivalente a letrec. A quanto pare questa peculiarità dell'ambiente del MIT ha influenzato il libro ... o forse è il contrario.

Altri suggerimenti

Non si può avere definisce interne che dipendono l'uno dall'altro; la specifica lingua afferma esplicitamente questo (R5RS 5.2.2):

  

... deve essere possibile valutare ciascun espressione di ogni definizione interno in un corpo senza assegnare o si riferisce al valore di qualsiasi variabile in fase di definizione.

Si può pensare a questo come se l'interprete sta raccogliendo tutte le definisce e la loro valutazione prima del corpo in un ordine casuale. Perché l'ordine è casuale, non ci può essere alcun interdipendenze se ci si aspetta che al lavoro.

C'è anche una nota in calce attaccato alla definizione SOLVE (# 71) che dice che non sta andando a lavorare su tutti i regimi.

Devi scrivere il codice in modo che una definizione è molto chiaramente nel campo di applicazione l'altro, come con ville nidificato:

(define (solve f y0 dt)
  (let ((y (integral (delay dy) y0 dt)))
    (let ((dy (stream-map f y)))
      y)))

Seguendo l'idea nei commenti (in riferimento alla citazione da R5RS 4.2.2) ora ho avvolto le definizioni di "y" e "dy" in (lambda () ...)s:

  (define (solve f y0 dt)
    (define (y) (integral (delay (dy)) y0 dt))
    (define (dy) (stream-map f (y)))
    (y))

Questo assicura che la parte <init> di ogni definizione può essere valutato senza fare riferimento alle variabili definite circolari poiché le definizioni sono procedure anziché espressioni con altre variabili come nel caso originale.

Ora il codice è certamente molto più lento (dal momento che le funzioni avranno avvolto ricorsivamente) e le esigenze dimensione dello stack da un aumento, ma le seguenti opere e produce il risultato corretto:

  (debug-set! stack 2000000)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

Con una modifica simile, codice esempio Michał funziona appena uno procedure definisce piuttosto variabili:

  (let ()
    (define (xs) '(1 2 3))
    (define (ys) (map (lambda (x) (+ x 1)) (xs)))
    (ys))

lavora su Guile 1.8.6.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top