Petit SICP / question Scheme (état local)
Question
Je lis le livre en fait pour le plaisir, mais il peut être considéré comme devoirs. En tout état de cause, je ne me sens pas à l'aise avec les variables d'état locales du tout avec cette langue ... Prenons par exemple le code suivant:
(define flip
(let ((count 0))
(lambda ()
(if (= 0 count)
(begin (set! count 1) count)
(begin (set! count 0) count)))))
pourquoi est-ce autre code entre 1 et 0? compte est donné la valeur de 0 à chaque fois que cette fonction est appelée! Un équivalent python serait:
class Flip:
def __init__(self):
pass
def __call__(self):
count = 0
if count == 0:
count = 1
return count
else:
count = 0
return count
renvoie la même chose à chaque fois. Je suis confus ...
La solution
J'ai un peu d'expérience avec les compilateurs d'écriture pour les langages fonctionnels, donc peut-être une brève description de la façon dont cette fonction est stockée / représentée dans la mémoire est en ordre. Chaque fonction peut à peu près être considéré comme une paire (E, F) où E est l'ensemble des variables libres et F est le « code » de la fonction elle-même. Lorsque vous appelez la fonction, il prend les valeurs dans E et remplace celles des variables F, et exécute ensuite le code en utilisant ces valeurs.
Alors, où votre exemple est concerné, vous avez défini la variable « flip » pour être la fonction renvoyée par votre expression let. Cette fonction est la substance dans votre lambda. Parce que est défini « compte » en dehors du lambda, il est une variable libre, donc il est stocké dans l'environnement de la fonction. Ensuite, chaque fois que vous appelez (FLIP), l'interprète va au code dans le lambda, voit qu'il doit rechercher la valeur de « compte » dans l'environnement, le fait que, le change, et retourne. Voilà pourquoi chaque fois que vous l'appelez, la valeur stockée dans « count » persiste.
Si vous voulez compter à zéro chaque fois que vous appelez chiquenaude, mettez l'expression let dans le lambda, il est donc une variable liée au lieu d'une variable libre.
Autres conseils
Le lambda est une fermeture. Il est une fonction qui fait référence à une variable libre (nombre), qui, sans être définie localement ou l'un des paramètres, est lié à la plus proche enfermant environnement lexical.
La fonction appelée est le lambda, non « flip ». Flip est juste un nom que vous avez donné au lambda qui est revenu de l'expression (... laisser).
En ce qui concerne le Python, je ne sais pas la langue, mais il semble que le nombre devrait être un membre de l'objet flip, pas une variable locale à Appel .
Parce que votre fonction flip en fait retourne une fonction (qui est définie à l'intérieur lambda)
Chaque fois que vous appelez la fonction renvoyée, il modifie son environnement.
Si vous y pensez let crée l'environnement (et initialise à 0 compte) qu'une seule fois -. Lorsque la fonction lambda est retourné
Dans un sens lambda crée un objet de fonction pour vous qui utilise l'environnement, dont le dernier cadre a été initialisé dans let avec un nombre variable unique. Chaque fois que vous appelez votre fonction modifie son environnement. Si vous appelez retourner une deuxième fois, il retourne un autre objet de fonction avec environnement différent. (Nombre initialisé à 0) Vous pouvez ensuite basculer les deux foncteurs indépendamment.
Si vous voulez undestand pleinement comment cela fonctionne, vous devriez lire sur modèle environmantal .
il est plus comme
class Flip:
def __init__(self):
self.count = 0
def __call__(self):
if self.count == 0:
self.count = 1
return self.count
else:
self.count = 0
return self.count
Mise à jour avec plus d'explications:
La fonction dans le schéma est une fermeture qui « se ferme » autour de la count
libre variable, qui est définie dans le cadre extérieur. La façon dont count
est définie dans un let
avec juste la fonction que le corps, signifie que la fonction est la seule chose qui peut y accéder -. count
faire efficacement une sorte d'état mutable privé qui est attaché à la fonction
Ceci est la façon dont « les objets » sont traditionnellement créés dans le schéma en SICP - d'avoir un let
définir un groupe de variables (les variables d'instance, initialisées à leurs valeurs initiales), et dans le corps définissent un tas de fonctions sont des « méthodes » qui ont un accès partagé aux variables d'instance. Voilà pourquoi il est naturel ici d'utiliser une classe Python pour représenter ce qui se passe, avec count
étant une variable d'instance.
Une traduction plus littérale en Python 3.x serait quelque chose comme ceci (notez qu'il est approximative que Python n'a pas de let
(de portée limitée déclaration variable locale) la syntaxe et les lambda
s de Python ne peut pas être utilisé parce qu'ils ne prennent pas des déclarations):
count = 0
def flip():
nonlocal count
if count == 0:
count = 1
return count
else:
count = 0
return count
# pretend count isn't in scope after this
Le problème avec le code d'origine est qu'il a une forte influence du style impératif. Une solution plus idiomatiques sera:
(define (flip)
(let ((flag #t))
(lambda ()
(set! flag (not flag))
(if flag 1 0))))
Pour répondre à la question dans le commentaire de ooboo, vous voulez une fonction qui renvoie une fonction
(define make-flipper
(lambda ()
(let ((count 0))
(lambda ()
(let ((v count))
(set! count (- 1 count))
v)))))
;; test it
(let ((flip-1 (make-flipper)))
(format #t "~s~%" (flip-1))
(format #t "~s~%" (flip-1))
(format #t "~s~%" (flip-1))
(let ((flip-2 (make-flipper)))
(format #t "~s~%" (flip-2))
(format #t "~s~%" (flip-2))
(format #t "~s~%" (flip-2))))
Vous pouvez trivialement changer le jeu! ligne afin qu'il devienne un compteur, pas un Flipper (plus utile).