34 votes

Comment fonctionne le puzzle yin-yang?

J'essaie de saisir la sémantique de call / cc dans Scheme, et la page Wikipedia sur les suites montre le puzzle yin-yang comme exemple:

 (let* ((yin
         ((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
       (yang
         ((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))) )
    (yin yang))
 

Il devrait afficher @*@**@***@****@... , mais je ne comprends pas pourquoi; Je m'attendrais à ce qu'il produise @*@********* ...

Quelqu'un peut-il expliquer en détail pourquoi le puzzle yin-yang fonctionne comme il fonctionne?

19voto

romkyns Points 17295

La Compréhension De Régime

Je pense qu'au moins la moitié du problème avec la compréhension de ce casse-tête est le Régime de la syntaxe, dont la plupart ne sont pas familiers avec.

Tout d'abord, personnellement, je trouve l' call/cc x d'être plus difficile à comprendre que l'alternative équivalente, x get/cc. - Il encore des appels de x, en lui transmettant le courant de la poursuite, mais d'une certaine manière est plus susceptible d'être représentée dans mes circuits du cerveau.

Avec cela à l'esprit, la construction (call-with-current-continuation (lambda (c) c)) devient simplement get-cc. Nous sommes maintenant à ceci:

(let* ((yin
         ((lambda (cc) (display #\@) cc) get-cc))
       (yang
         ((lambda (cc) (display #\*) cc) get-cc)) )
    (yin yang))

La prochaine étape est le corps de l'intérieur de la lambda. (display #\@) cc, dans le plus familier de la syntaxe (pour moi, de toute façon) désigne print @; return cc;. Pendant que nous y sommes, nous allons également réécrire lambda (cc) body comme function (arg) { body }, enlever un tas de parenthèses, et le changement des appels de fonction pour le C-comme la syntaxe, pour obtenir ceci:

(let*  yin =
         (function(arg) { print @; return arg; })(get-cc)
       yang =
         (function(arg) { print *; return arg; })(get-cc)
    yin(yang))

Il commence à faire plus de sens maintenant. C'est maintenant une petite étape à réécrire entièrement en C-syntaxe (ou JavaScript-comme, si vous préférez), pour obtenir ceci:

var yin, yang;
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);

La partie la plus difficile est maintenant terminée, nous avons décodé ce de Régime! C'est juste une blague; c'était dur parce que je n'avais aucune expérience précédente avec le Régime. Donc, nous allons apprendre à essayer de comprendre comment cela fonctionne réellement.

Une couche d'apprêt sur les continuations

Observer l'étrangement formulée de base du yin et du yang: il définit une fonction , puis immédiatement à l'appelle. Ça ressemble à de la (function(a,b) { return a+b; })(2, 3), ce qui peut être simplifié à l' 5. Mais de simplifier les appels à l'intérieur de yin/yang serait une erreur, car nous ne sommes pas en passant une valeur ordinaire. Nous passons à la fonction d'une poursuite.

Une suite est une drôle de bête à première vue. Envisager le plus simple programme:

var x = get-cc;
print x;
x(5);

Initialement, x est défini à la poursuite de l'objet (l'ours avec moi), et print x est exécuté, l'impression de quelque chose comme <ContinuationObject>. So far So good.

Mais une continuation, c'est comme une fonction; elle peut être appelée avec un argument. Ce qu'il fait est: prendre l'argument, puis sauter à l'endroit où la poursuite a été créé, la restauration de tout contexte, et de faire en sorte qu' get-cc retourne cet argument.

Dans notre exemple, l'argument est - 5, nous avons donc essentiellement de sauter de nouveau dans le milieu de l' var x = get-cc déclaration, seulement cette fois - get-cc retours 5. Donc, x devient 5, et la prochaine déclaration va à l'impression 5. Après que nous essayons de faire appel 5(5), ce qui est une erreur de type, et le programme se bloque.

Observer que l'appel de la poursuite est un saut, pas un appel. Il ne revient jamais en arrière à l'endroit où la poursuite a été appelé. Ce qui est important.

Comment fonctionne le programme

Si vous avez suivi, puis ne pas obtenir vos espoirs vers le haut: cette partie est vraiment le plus dur. Voici notre programme de nouveau, la suppression des déclarations de variables parce que c'est de la pseudo-code de toute façon:

yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);

La première ligne du temps 1 et 2 sont touchés, ils sont simple maintenant: obtenir la poursuite, l'appel de la fonction(arg), imprimer @, le rendement, le magasin de poursuite en yin. Même avec yang. Nous avons maintenant imprimé @*.

Ensuite, nous appelons à la poursuite en yin, passant yang. Cela nous fait sauter à la ligne 1, à droite à l'intérieur que les cc, et le faire revenir yang à la place. La valeur de yang est maintenant passé dans la fonction, ce qui imprime @, puis renvoie la valeur de yang. Maintenant, yin est affectée à la poursuite qu' yang a. Ensuite, nous avons juste passer à la ligne 2: c/c, imprimer *, stocker le c/c en yang. Nous avons maintenant @*@*. Et enfin, nous allons à la ligne 3.

Rappelez-vous que yin maintenant a la poursuite de lorsque la ligne 2 a été exécuté. Afin de nous sauter à la ligne 2, l'impression d'un second * et la mise à jour de yang. Nous avons maintenant @*@**. Enfin, l'appel de l' yin continuation de nouveau, qui va sauter à la ligne 1, l'impression d'un @. Et ainsi de suite. Franchement, à ce stade, mon cerveau déclenche une exception OutOfMemory et j'en perds la trace de tout. Mais nous avons au moins à l' @*@**!

C'est dur à suivre, et encore plus difficile à expliquer, évidemment. La meilleure façon de le faire serait d'étape à travers elle dans un débogueur qui peut représenter des suites, mais hélas, je ne sais pas du tout. J'espère que vous avez apprécié ce; je n'ai certainement.

16voto

hzap Points 988

Je ne pense pas que je le comprends totalement, mais je ne peux que penser d'un (très attrayante) explication:

  • Le premier @ et * sont imprimées lorsqu' yin et yang sont d'abord liés à l' let*. (yin yang) est appliqué, et il remonte vers le haut, à droite après le premier appel/cc est fini.
  • La prochaine @ et * sont imprimés, puis un autre * est imprimé parce que ce temps de grâce, yin re-lié à la valeur de la deuxième appel/cc.
  • (yin yang) est appliqué à nouveau, mais cette fois, c'est l'exécution à l'origine, yangs'environnement, où yin est liée à la première call/cc, de sorte contrôle remonte à l'impression d'un autre @. L' yang argument contient la poursuite qui a été re-capturé sur le deuxième passage, qui, comme nous l'avons déjà vu, entraînera l'impression **. Donc, sur cette troisième phase, @* sera imprimé, puis ce double-étoiles-impression poursuite est appelé, de sorte qu'il se retrouve avec 3 étoiles, et de ce triple-star de la continuation re-capturé, ...

6voto

philcolbourn Points 1182

Rêveries d'abord, réponse possible à la fin.

Je pense que le code peut être ré-écrit comme ceci:

; call (yin yang)
(define (yy yin yang) (yin yang))

; run (call-yy) to set it off
(define (call-yy)
    (yy
        ( (lambda (cc) (display #\@) cc) (call/cc (lambda (c) c)) )
        ( (lambda (cc) (display #\*) cc) (call/cc (lambda (c) c)) )
     )
)

Ou avec un certain afficher instructions pour vous aider à voir ce qui se passe:

; create current continuation and tell us when you do
(define (ccc)
    (display "call/cc=")
    (call-with-current-continuation (lambda (c) (display c) (newline) c))
)

; call (yin yang)
(define (yy yin yang) (yin yang))

; run (call-yy) to set it off
(define (call-yy)
    (yy
        ( (lambda (cc) (display "yin : ") (display #\@) (display cc) (newline) cc) 
            (ccc) )
        ( (lambda (cc) (display "yang : ") (display #\*) (display cc) (newline) cc) 
            (ccc) )
     )
)

Ou comme ceci:

(define (ccc2) (call/cc (lambda (c) c)) )
(define (call-yy2)
    (
        ( (lambda (cc) (display #\@) cc) (ccc2) )
        ( (lambda (cc) (display #\*) cc) (ccc2) )
    )
)

Réponse Possible

Cela peut ne pas être de droite, mais je vais avoir un aller.

Je pense que le point essentiel est qu'un "appelé" continuation retourne la pile pour certains à l'état précédent, comme si rien ne s'était passé. Bien sûr, il ne sait pas que nous avons suivi en les affichant, @ et * caractères.

Nous avons d'abord définir yin être un prolongement A qui fera ce:

1. restore the stack to some previous point
2. display @
3. assign a continuation to yin
4. compute a continuation X, display * and assign X to yang
5. evaluate yin with the continuation value of yang - (yin yang)

Mais si nous appelons yang de continuation, ce qui se passe:

1. restore the stack to some point where yin was defined
2. display *
3. assign a continuation to yang
4. evaluate yin with the continuation value of yang - (yin yang)

Nous commençons ici.

Première fois que vous obtenez yin=A et yang=B comme yin et yang sont en cours d'initialisation.

The output is @*

(Les deux A et B continuations sont calculées.)

Maintenant, (yin yang) est évaluée comme (A B) , pour la première fois.

Nous savons ce qu' A n'. Il fait ceci:

1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B, we don't compute it.
4. compute another continuation B', display * and assign B' to yang

The output is now @*@*

5. evaluate yin (B) with the continuation value of yang (B')

Maintenant, (yin yang) est évaluée comme (B B').

Nous savons ce qu' B n'. Il fait ceci:

1. restore the stack - back to the point where yin was already initialised.
2. display *
3. assign a continuation to yang - this time, it is B'

The output is now @*@**

4. evaluate yin with the continuation value of yang (B')

Depuis la pile a été restaurée au point où yin=A, (yin yang) est évaluée comme (A B').

Nous savons ce qu' A n'. Il fait ceci:

1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B', we don't compute it.
4. compute another continuation B", display * and assign B" to yang

The output is now @*@**@*

5. evaluate yin (B') with the continuation value of yang (B")

Nous savons ce qu' B' n'. Il fait ceci:

1. restore the stack - back to the point where yin=B.
2. display *
3. assign a continuation to yang - this time, it is B"

The output is now @*@**@**

4. evaluate yin (B) with the continuation value of yang (B")

Maintenant, (yin yang) est évaluée comme (B B").

Nous savons ce qu' B n'. Il fait ceci:

1. restore the stack - back to the point where yin=A and yang were being initialised.
2. display *
3. assign a continuation to yang - this time, it is B'"

The output is now @*@**@***

4. evaluate yin with the continuation value of yang (B'")

Depuis la pile a été restaurée au point où yin=A, (yin yang) est évaluée comme (A B'").

.......

Je pense que nous avons un modèle maintenant.

Chaque fois que nous appelons (yin yang) nous boucle à travers une pile de B continuations jusqu'à ce que nous obtenons lorsque yin=A et nous affichons @. L', nous bouclons la pile de B continuations de la rédaction d'un * à chaque fois.

(Je serais vraiment heureux si c'est à peu près la droite!)

Merci pour la question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X