2010-03-23 17 views

Répondre

4

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] 
5

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.

+0

Vous êtes toujours une aide, merci Lukas :) –

+0

Dans ce cas, la restauration du contexte est fondamentalement l'équivalent de GOTO –

+1

Sans récursion infinie, sans un nombre infini de déclaration et sans la possibilité de reprendre un contexte semble impossible. –

1

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.

1

Il suffit de faire:

BlockClousure >> whileTrue: unBloc

Valeur propre ifTrue: [ aBlocage. thisContext redémarrage. "redémarrer sur pharo, réinitialiser sur VW"]