SmallTalk a le whileTrue: -Message implémenté par récursion (dans VisualWorks) ou par compilateur-inlining (dans Squeak/Pharo). Existe-t-il un moyen de définir une telle méthode sans utiliser l'une d'entre elles? Si non, y a-t-il une preuve de cela disponible quelque part?Existe-t-il un moyen, dans un langage de message uniquement, de définir un message whileTrue sans récursivité ni astuces de compilation?
Répondre
whileTrue: & whileFalse: retourne toujours zéro. par exemple. s'il y a une définition récursive normale:
whileTrue: aBlock
^self value ifTrue: [self whileTrue: aBlock]
le ifTrue: retourne nul si l'auto valeur est fausse et si la valeur doit toujours être nul. Cela se reflète dans l'optimisation du compilateur. Le livre bleu original définition Smalltalk-80 V2 est
whileTrue: aBlock
"Evaluate the argument, aBlock, as long as the value
of the receiver is true. Ordinarily compiled in-line.
But could also be done in Smalltalk as follows"
^self value
ifTrue:
[aBlock value.
self whileTrue: aBlock]
Il suffit donc de changer la vôtre à
BlockContext>>myWhileTrue: aBlock
| start |
start := thisContext pc.
self value ifFalse: [^nil ].
aBlock value.
thisContext pc: start
ou ??
BlockContext>>myWhileTrue: aBlock
| start |
start := thisContext pc.
^self value ifTrue:
[aBlock value.
thisContext pc: start]
Mais hélas ces deux plantage de la machine virtuelle quelque temps après la deuxième itération, car thisContext pc ne répond pas à l'ordinateur sur la prochaine itération, mais quel que soit le haut de la pile est :)
Cependant, la suivant fonctionne:
ContextPart methods for controlling
label
^{ pc. stackp }
goto: aLabel
"N.B. we *must* answer label so that the
top of stack is aLabel as it is when we send label"
pc := aLabel at: 1.
self stackp: (aLabel at: 2).
^aLabel
BlockContext>>myWhileTrue: aBlock
| label |
label := thisContext label.
self value ifFalse: [^nil].
aBlock value.
thisContext goto: label
BlockClosure>>myWhileTrue: aBlock
| label |
label := thisContext label.
^self value ifTrue:
[aBlock value.
thisContext goto: label]
Je propose la solution suivante:
BlockContext>>myWhileTrue: aBlock
| start |
start := thisContext pc.
self value ifFalse: [^self ].
aBlock value.
thisContext pc: start
Au lieu d'utiliser des astuces de récursion et compilateur, le code ci-dessus utilise la réflexion sur la pile d'exécution. Avant le démarrage de la boucle, la méthode stocke le compteur de programmes en cours dans une variable temporaire et la réinitialise à la fin pour revenir au début de la méthode. Dans certaines implémentations de Smalltalk, une telle approche peut être lente car certains dialectes Smalltalk réifient la pile uniquement sur demande, mais dans Pharo/Squeak, cette astuce est tout à fait réalisable.
Notez que le code ci-dessus ne répond pas au résultat de l'activation du dernier bloc comme l'implémentation d'origine de #whileTrue: fait. Cela devrait être assez facile à résoudre.
Vous pouvez également utiliser un gestionnaire d'exceptions pour le faire revenir au début, mais cela pourrait être considéré comme une tricherie si le code de gestion des exceptions utilisait un élément whileTrue: ou un autre élément de construction en boucle. Donc, fondamentalement, la question se résume à savoir si vous pouvez implémenter une boucle sans goto ou récursion, et je pense que la réponse à cette question est non. Donc, si la récursivité est interdite, il vous reste à essayer de bricoler un goto à partir de techniques comme la définition de la méthode pc ou l'utilisation d'une exception.
Il suffit de faire:
BlockClousure >> whileTrue: unBloc
Valeur propre ifTrue: [ aBlocage. thisContext redémarrage. "redémarrer sur pharo, réinitialiser sur VW"]
Vous êtes toujours une aide, merci Lukas :) –
Dans ce cas, la restauration du contexte est fondamentalement l'équivalent de GOTO –
Sans récursion infinie, sans un nombre infini de déclaration et sans la possibilité de reprendre un contexte semble impossible. –