2010-05-13 6 views
2

Je travaille actuellement à travers SICP en utilisant Guile comme langue principale pour les exercices. J'ai trouvé un comportement étrange lors de la mise en place des exercices au chapitre 3.5. J'ai reproduit ce comportement en utilisant Guile 1.4, Guile 1.8.6 et Guile 1.8.7 sur une variété de plateformes et je suis certain qu'il n'est pas spécifique à ma configuration.Problème avec la définition circulaire dans le schéma

Ce code fonctionne très bien (et calcule e):

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

Le code suivant devrait donner un résultat identique:

(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) 

Mais il donne le message d'erreur:

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) 

Ainsi, lorsqu'ils sont intégrés dans une définition de procédure, le y ...) ne fonctionne pas, alors qu'en dehors de la procédure dans l'environnement global au REPL cela fonctionne bien.

Qu'est-ce que je fais mal ici? Je peux également afficher le code auxiliaire (c'est-à-dire les définitions de l'intégrale, de la carte de flux, etc.) si nécessaire. À l'exception du code dépendant du système pour contre-flux, ils sont tous dans le livre. Ma propre mise en œuvre de contre-courant pour Guile est la suivante:

(define-macro (cons-stream a b) 
    `(cons ,a (delay ,b))) 
+0

Je me souviens d'avertissements sévères de la part d'un instructeur du genre NEVER USE INTERNE DEFINE, alors peut-être que cela cause le problème. –

+0

Selon R5RS chapitre 5.2: "Ils [définitions] sont valables uniquement au niveau supérieur d'un et au début d'un ." Donc, selon la norme linguistique, le code que j'ai posté est autorisé. Encore plus: Au chapitre 5.2.2, ils donnent explicitement un exemple de l'équivalence syntaxique entre letrec et internal defined. Cela dit, la version syntaxiquement équivalente du code ci-dessus utilisant letrec résultera également dans le même message d'erreur. Le seul moyen que j'ai trouvé jusqu'ici pour que le code fonctionne correctement est de définir y et dy dans l'environnement global du REPL. – user8472

Répondre

1

La principale différence entre ce qui se passe lorsque vous évaluez les définitions un par un au REPL et quand vous les placez à l'intérieur solve est que dans le premier cas , ils sont évalués séquentiellement, ainsi l'y l'expression (stream-map <some-function> y) fait déjà partie de la portée, alors qu'avec les définitions internes ou letrec, elle n'est pas encore disponible.

Bizarrement, MIT Scheme, que j'ai utilisé lors de la traversée SICP, avait pas de problème à l'époque et traite encore letrec et définit différemment internes:

;; 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) 

Je ne suis pas sûr de l'original « révisée Rapport sur Algorithmic Language Scheme "ou R2RS, mais au moins à partir de R3RS sur les définitions internes étaient censés être équivalents à letrec. Apparemment, cette particularité de l'environnement du MIT a influencé le livre ... ou peut-être l'inverse.

+0

Alors pourquoi voudrais-je utiliser des définitions internes du tout? La restriction mentionnée dans R5RS (voir mon commentaire ci-dessus) pour autoriser les définitions seulement au début d'un serait inutile si elles étaient indisponibles pour le reste du corps. Pire encore: lorsque je tape les deux blocs d'expressions cités dans mon post * en séquence * (ie, avec "y" et "dy" déjà définis dans l'environnement global), je reçois toujours le même message d'erreur lors de l'évaluation (stream-ref (solve ...) 1000), même si Scheme est supposé se référer à celui de l'environnement global quand le local n'est pas disponible. – user8472

+0

Le schéma n'est * pas * censé se référer à la liaison globale; Les liaisons 'letrec' (et les définitions internes, qui sont équivalentes) sont destinées à être mutuellement récursives. * Cependant *, en citant R5RS: "Tout comme pour l'expression letrec équivalente, il doit être possible d'évaluer chaque de chaque définition interne dans un sans affecter ni se référer à la valeur de en cours de définition." Il y a toutes sortes de raisons pour lesquelles les expressions 'letrec' sont utiles et les définitions internes, étant simplement une syntaxe alternative pour' letrec', sont utiles dans les mêmes situations. –

+0

Quoi qu'il en soit, autant que je sache, c'est ce qui ne va pas avec le code à portée de main. Vous pourriez essayer de réparer les choses avec 'delay' /' force'. La logique qui sous-tend le fait que R5RS impose un tel comportement est une question différente, et je ne suis pas sûr d'être prêt à en discuter. –

2

Vous ne pouvez pas avoir des DEFINE internes qui dépendent les unes des autres; la spécification de langage indique explicitement (r5rs 5.2.2):

... il doit être possible d'évaluer chaque expression de chaque définition interne dans un corps sans affectation ou se référant à la valeur de tout variable en cours de définition.

Vous pouvez penser à ceci comme si l'interpréteur collectait toutes les DEFINES et les évaluait avant le corps dans un ordre aléatoire. Parce que l'ordre est aléatoire, il ne peut y avoir aucune interdépendance si vous vous attendez à ce qu'il fonctionne.

Il y a même une note de bas de page attachée à la définition SOLVE (# 71) qui dit que ça ne marchera pas sur tous les schémas.

Vous devez écrire le code de sorte que l'une définition est très clairement dans le cadre de l'autre, comme avec emboîtés LETS:

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

Excepté le 'dy 'dans' (delay dy) 'est censé se référer à le 'dy' défini ci-dessous ... Une utilisation prudente de' delay' avec un 'letrec' est susceptible d'être nécessaire. Bonne capture sur la note, cependant. –

+0

Oui, merci d'avoir signalé la note de bas de page. Il y a un commentaire similaire dans R5RS 4.2.2: «Une restriction sur' letrec 'est très importante: il doit être possible d'évaluer chaque sans assigner ou se référer à la valeur de Si cette restriction est violée, alors il est une erreur." La définition circulaire fait certainement référence à une * valeur * d'une variable. Alors est-il possible qu'une telle définition circulaire ne soit pas valable Scheme en premier lieu? Si c'est le cas, encapsuler le code dans les procédures via (lambda() ...) pourrait être la solution "correcte". – user8472

0

Suivant l'idée dans les commentaires (se référant à la citation de r5rs 4.2 .2) Je l'ai maintenant enveloppé les définitions de « y » et « dy » dans (lambda() ...) s:

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

Cela fait en sorte que que la partie <init> de chaque définition peut être évaluée sans ref errer aux variables définies circulaires puisque les définitions sont des procédures plutôt que des expressions avec d'autres variables comme dans le cas original.

Maintenant, le code est certainement beaucoup plus lent (puisque les fonctions sera enveloppé récursive) et la taille de la pile doit être augmenté, mais les travaux suivants et produit le résultat correct:

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

Avec une modification similaire, le code exemple de Michał fonctionne dès que l'on définit des procédures plutôt que des variables:

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

travaux sur Guile 1.8.6. Que se passe-t-il si vous utilisez `letrec` au lieu de` define` interne?

Questions connexes