Просто чтобы немного уточнить ответ @Peter:
Итерация
1 to: 10 do: [:each | dictionary at: each put: each]
отправляет + 1 в переменную блока each каждый раз, когда он повторяет блок.
Мы не видим + 1 в исходном коде, но на самом деле это происходит за кулисами. Причина обнаружения такой скрытой отправки заключается в том, что Smalltalk анализирует не исходный код, а CompiledMethod, и делает это, просматривая литеральный фрейм и, как говорит Питер, также байт-коды.
Например, путем компиляции и проверки метода
m
1 to: 10 do: [:i | i foo]
мы можем видеть, что вкладка промежуточного представления Ir гласит:
1. label: 1
2. pushLiteral: 1
3. popIntoTemp: #i
4. goto: 2
5. label: 2
6. pushTemp: #i
7. pushLiteral: 10
8. send: #'<='
9. if: false goto: 4 else: 3
10. label: 3
11. pushTemp: #i
12. send: #foo
13. popTop
14. pushTemp: #i
15. pushLiteral: 1
16. send: #+ "Here we sum 1 to i"
17. popIntoTemp: #i
18. goto: 2
19. label: 4
20. returnReceiver
Строки 15 и 16 представляют собой + 1, которое происходит на самом деле. Обратите внимание, что символ #+ не находится в литеральной рамке (вкладка Raw инспектора). Его нет и в AST, потому что + отсутствует в исходном коде
Почему это происходит?
Есть несколько специальных сообщений, #to:do: одно из которых, которые встраиваются, когда ваш код их отправляет. Встраивание метода означает включение его байт-кодов в отправителя, а не их фактическую отправку.
Например, если ваш код читает
self foo.
1 to: 10 do: [:i | i foo].
self bar
ваш метод имеет 4 отправки: foo, to:do:, foo (снова) и bar. Однако компилятор создаст только 3 отправки foo, foo и bar и включит байт-коды to:do: на место.
И учитывая, что to:do: необходимо суммировать 1 с аргументом блока i, send +, которое фактически отправляется, обнаруживается как встречающееся в вашем методе (потому что на самом деле это так).
Однако, если вы перепишете метод как
m
| block |
block := [:i | i foo].
self foo.
1 to: 10 do: block
self bar
компилятор откажется встраивать to:do: и отправит его как обычное сообщение. В результате ваш метод больше не будет отправителем +.
Если to:do: не отправляется, почему мой метод является его отправителем?
Это более тонкий вопрос. Как мы видели, когда to:do: встроено, оно не отправляется. Как же тогда возможно, что мой метод распознан как отправитель to:do:?
Ну, причина в том, что компилятор все равно добавит символ #to:do: в литеральный фрейм вашего метода. И это будет сделано только для того, чтобы ваш метод был найден как отправитель to:do:. Неважно, действительно ли ваш метод отправляет to:do: или нет, метод встраивания — это просто оптимизация. На более высоком уровне абстракции мы все хотим видеть, что наш метод является «отправителем» to:do:, отсюда и «трюк» добавления его в литеральный фрейм.
person
Leandro Caniglia
schedule
21.01.2017