2010-03-23 16 views

Odpowiedz

4

whileTrue: & whileFalse: zawsze zwraca zero. np. jeśli nie jest to normalny rekurencyjna definicja:

whileTrue: aBlock 
    ^self value ifTrue: [self whileTrue: aBlock] 

ifTrue: zwróci nil, jeśli wartość samo jest fałszywe, a więc wartość powinna być zawsze zero. Znajduje to odzwierciedlenie w optymalizacji kompilatora. Oryginalny blue book Smalltalk-80 V2 definicja jest

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] 

Więc po prostu zmienić, aby

BlockContext>>myWhileTrue: aBlock 
    | start | 
    start := thisContext pc. 
    self value ifFalse: [^nil ]. 
    aBlock value. 
    thisContext pc: start 

lub ??

BlockContext>>myWhileTrue: aBlock 
    | start | 
    start := thisContext pc. 
    ^self value ifTrue: 
     [aBlock value. 
     thisContext pc: start] 

Ale niestety obie te katastrofy VM kiedyś po drugiej iteracji ponieważ thisContext pc nie odpowiada komputera na następnej iteracji, ale zamiast cokolwiek wierzchołek stosu jest :)

Jednak następujące działa:

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

proponuję następujące rozwiązanie:

BlockContext>>myWhileTrue: aBlock 
    | start | 
    start := thisContext pc. 
    self value ifFalse: [^self ]. 
    aBlock value. 
    thisContext pc: start 

Zamiast rekursji i kompilator sztuczek, powyższy kod wykorzystujący odbicie na stosie wykonania. Przed rozpoczęciem pętli metoda przechowuje bieżący licznik programu w zmiennej tymczasowej i resetuje go na końcu, aby powrócić do początku metody. W niektórych implementacjach Smalltalk takie podejście może być powolne, ponieważ niektóre dialekty Smalltalk potwierdzają tylko stos na żądanie, ale w Pharo/Squeak ta sztuczka jest całkiem praktyczna.

Uwaga, powyższy kod nie odpowiada na wynik ostatniej aktywacji bloku, ponieważ oryginalna implementacja #whileTrue: robi. Jednak powinno to być łatwe do naprawienia.

+0

Jesteś zawsze pomoc, dzięki Lukas :) –

+0

W tym przypadku przywrócenie kontekstu jest w zasadzie odpowiednikiem GOTO –

+1

Bez nieskończonej rekursji, bez nieskończonej liczby instrukcji i bez możliwość wznowienia kontekstu wydaje się niemożliwa. –

1

Można również użyć procedury obsługi wyjątku, aby wrócić do początku, ale może to być traktowane jako oszustwo, jeśli kod obsługi wyjątków użyty został po raz drugi: lub w innym miejscu. Zasadniczo, pytanie sprowadza się do tego, czy można zaimplementować pętlę bez goto lub rekursji i myślę, że odpowiedź na to pytanie brzmi "nie". Więc jeśli rekursja jest zabroniona, pozostaje ci próba zgarnięcia razem technik, takich jak ustawianie metody PC lub użycie wyjątku.

1

Wystarczy zrobić:

BlockClousure >> WhileTrue: aBlock

wartość własna ifTrue: [ Wartość aBlock. thisContext restart. "restart na pharo, reset na VW"]

Powiązane problemy