Есть ли способ на языке только сообщений определить сообщение whileTrue без рекурсии или трюков с компилятором? - PullRequest
6 голосов
/ 23 марта 2010

В Smalltalk имеется whileTrue: -Message, реализованное с помощью рекурсии (в VisualWorks) или с помощью компиляции (в Squeak / Pharo). Есть ли способ определить такой метод без использования одного из них? Если нет, то есть ли где-нибудь доказательства этого?

Ответы [ 4 ]

5 голосов
/ 23 марта 2010

Предлагаю следующее решение:

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

Вместо использования трюков с рекурсией и компилятором в приведенном выше коде используется отражение в стеке выполнения. Перед запуском цикла метод сохраняет текущий программный счетчик во временной переменной и сбрасывает его в конце, чтобы вернуться к началу метода. В некоторых реализациях Smalltalk такой подход может быть медленным, так как некоторые диалекты Smalltalk ограничивают стек только по требованию, но в Pharo / Squeak этот прием вполне осуществим.

Обратите внимание, что приведенный выше код не отвечает на результат последней активации блока, как это делает оригинальная реализация # whileTrue :. Это должно быть достаточно легко исправить, хотя.

4 голосов
/ 18 августа 2010

whileTrue: & whileFalse: всегда возвращать ноль. например если есть нормальное рекурсивное определение:

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

ifTrue: вернет nil, если self value равно false, поэтому значение всегда должно быть nil. Это отражено в оптимизации компилятора. Оригинальная синяя книга Smalltalk-80 V2 имеет определение

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]

Так что просто поменяйте свой на

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

или ??

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

Но, увы, оба из них приводят к аварийному завершению работы виртуальной машины через некоторое время после второй итерации, поскольку этот компьютер с контекстами не отвечает на ПК на следующей итерации, а вместо этого независимо от того, какой версией является стек:)

Однако работает следующее:

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]
1 голос
/ 19 августа 2010

Просто сделайте:

BlockClousure >> whileTrue: aBlock

собственное значение ifTrue: [значение aBlock.thisContext перезапустить."перезагрузить на pharo, сбросить на VW"]

1 голос
/ 27 марта 2010

Вы также можете использовать обработчик исключений, чтобы вернуть его в начало, но это может считаться мошенничеством, если в коде обработки исключений используется whileTrue: или другая циклическая конструкция. Итак, в основном, вопрос сводится к тому, можете ли вы реализовать цикл без goto или рекурсии, и я думаю, что ответ на этот вопрос - нет. Поэтому, если рекурсия запрещена, у вас остается попытка собрать воедино такие методы, как установка метода pc или использование исключения.

...