2017-09-06 3 views
2

Je pense que la compréhension de cette subtilité pourrait m'aider à comprendre le fonctionnement de la portée dans Scheme.Scheme/Guile: Auto-redéfinition variable à l'intérieur d'une fonction

Alors, pourquoi ne erreur Scheme si vous essayez de faire quelque chose comme:

(define (func n) 
    (define n (+ 1 n)) 
    n) 

Il ne fait que des erreurs liées à l'exécution lors de l'appel de la fonction.

La raison pour laquelle je trouve cela étrange est que Scheme autorise les redéfinitions, même à l'intérieur des fonctions. Par exemple, cela ne donne aucune erreur et renvoie toujours la valeur 5 comme prévu:

(define (func n) 
    (define n 5) 
    n) 

De plus, le schéma semble également soutenir l'auto-re-définition dans l'espace mondial. Par exemple:

(define a 5) 
(define a (+ 1 a)) 

donne aucune erreur et affiche "a" affichant "6" comme prévu

Alors pourquoi la même chose donne-t-elle une erreur d'exécution quand elle se produit dans une fonction (qui supporte la re-définition)? En d'autres termes: Pourquoi l'auto-redéfinition ne fonctionne-t-elle pas à l'intérieur d'une fonction?

Répondre

2

mondiale define

Tout d'abord, les programmes de haut niveau sont gérés par une autre partie de la mise en œuvre que dans une fonction et la définition d'une variable déjà définie n'est pas autorisé.

(define a 10) 
(define a 20) ; ERROR: duplicate definition for identifier 

Il peut arriver que cela fonctionne dans un REPL car il est fréquent de redéfinir des choses, mais lors de l'exécution des programmes ce qui est absolument interdit. Dans R5RS et avant que ce qui s'est passé est sous-spécifié et ne s'est pas inquiété puisque être la spécification en violant il n'est plus un programme de Scheme et les exécutants sont libres de faire ce qu'ils veulent. Le résultat est bien sûr que beaucoup de choses sous-spécifiées obtiennent un comportement spécifique à l'implémentation qui n'est pas portable ou stable.

Solution:

set! liaisons de mute:

(define a 10) 
(set! a 20) 

define dans un lambda (fonction, laissez, ...)

A define dans un lambda est quelque chose de complètement différent, géré par des parties complètement différentes de la mise en œuvre. Il est géré par la macro/forme spéciale lambda afin qu'il soit réécrite à un letrec*

A letrec* ou letrec est utilisé pour la fabrication des fonctions récursives de manière à les noms doivent être disponibles au moment où les expressions sont évaluées. À cause de cela quand vous definen il a déjà ombré le n que vous avez passé en argument. De plus, les implémenteurs de R6RS sont tenus de signaler une erreur lorsqu'une liaison qui n'est pas encore initialisée est évaluée et c'est probablement ce qui se passe.Avant R6RS étaient libres implémenteurs de faire ce qu'ils voulaient:

(define (func n) 
    (define n (+ n 1)) ; illegal since n hasn't got a value yet! 
    n) 

Cela devient en fait:

(define func 
    (lambda (n) 
    (let ((n 'undefined-blow-up-if-evaluated)) 
     (let ((tmpn (+ n 1))) 
     (set! n tmpn)) 
     n))) 

Maintenant, un compilateur peut voir qu'il viole les spécifications au moment de la compilation, mais de nombreuses implémentations ne sait pas avant qu'il ne s'exécute.

(func 5) ; ==> 42 

Parfaitement fin résultat dans R5RS si les exécutants ont un bon goût dans les livres. La différence dans la version vous avez dit des œuvres est que cela ne viole pas la règle d'évaluation n avant que le corps:

(define (func n) 
    (define n 5) 
    n) 

devient:

(define func 
    (lambda (n) 
    (let ((n 'undefined-blow-up-if-evaluated)) 
     (let ((tmpn 5)) ; n is never evaluated here! 
     (set! n tmpn)) 
     n))) 

Solutions

Utilisez un non nom en conflit

(define (func n) 
    (define new-n (+ n 1)) 
    new-n) 

Utilisez let. Il n'a pas sa propre liaison lorsque l'expression est évaluée:

(define (func n) 
    (let ((n (+ n 1))) 
    n))