Открыл свежее изображение Pharo 5, открыл Finder и поискал селектор +. Затем выберите "+" и нажмите "Отправители". Получил около 5000 результатов, большинство из них кажутся хорошими, однако при ближайшем рассмотрении в некоторых результатах отсутствует какая-либо ссылка на мое сообщение.
Я мог бы добавить немало, около 1700 из них кажутся ошибочными и не содержат ссылок на + что бы то ни было. Как вы думаете, в чем проблема? Вот один пример:
а>
Ответ 1 · рейтинг 6 · Peter Uhnak
Обновлять
Поиск идет как по текстовому исходному коду, так и по байт-коду; поэтому он показывает вам метод, потому что он нашел #+ в байт-коде, но браузер слишком ограничен, чтобы показать вам и текст, и байты, поэтому он показывает вам только текст (даже если совпадение было выполнено по байт-коду)
Что касается самого байт-кода, Pharo (повторно) компилирует метод каждый раз, когда вы его сохраняете; так, например, когда вы сохраняете следующий метод
Something>>loop
1 to: 10 do: [ :each | ]
система скомпилирует его, и когда вы проверите метод и посмотрите на байт-код, вы увидите это
а>
Я думаю, вы тоже можете написать байт-код вручную.
Оригинальный ответ
Потому что для некоторых специальных селекторов он также просматривает байт-код, который вы можете увидеть, когда проверяете сам метод.
а>
Это можно очень легко обнаружить менее чем за минуту, просмотрев код самого Pharo (как только вы освоитесь с Pharo):
#+ senders дает вам список отправителей селектора,
- so you look at the implementation of
senders and go through the available call chain (you can select selector with mouse or doubleclick and ctrl+n to show you the senders browser)
senders → allSendersOf: → thoroughWhichSelectorsReferTo:
- и там вы видите
.
thoroughWhichSelectorsReferTo: literal
"Answer a set of selectors whose methods access the argument as a
literal. Dives into the compact literal notation, making it slow but
thorough "
| selectors special byte |
"for speed we check the special selectors here once per class"
special := Smalltalk
hasSpecialSelector: literal
ifTrueSetByte: [ :value | byte := value ].
selectors := OrderedCollection new.
self selectorsAndMethodsDo: [ :sel :method |
((method refersToLiteral: literal) or: [special and: [method scanFor: byte]]) ifTrue: [selectors add: sel]].
^ selectors
Ответ 2 · рейтинг 4 · Leandro Caniglia
Просто чтобы немного уточнить ответ @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:, отсюда и "трюк" добавления его в литеральный фрейм.