打开一个新的Pharo 5图像,打开Finder并搜索+选择器。然后选择+并单击发件人。得到了大约5000个结果,其中大多数似乎都很好,但是仔细观察后,一些结果似乎缺少我发送的任何参考信息。
我可能会补充一点,其中约有1700个似乎是错误的,并且没有任何参考+什么。您认为这个问题是什么?这是一个例子:
答案 0 :(得分:6)
搜索通过文本源代码和字节码进行;所以它显示了方法,因为它在字节码中找到了#+
,但是浏览器太有限了,无法同时显示文本和字节,所以它只显示文本(即使匹配是在字节码上完成的)
至于字节码本身,Pharo(re)每次保存时都会编译一个方法;例如,当您保存以下方法时
Something>>loop
1 to: 10 do: [ :each | ]
系统会编译它,当你检查方法并查看字节码时,你会看到这个
我相信你也可以手工编写字节码。
因为对于某些特殊选择器,它还会查看字节码,您可以在检查方法本身时看到它。
通过查看Pharo本身的代码(一旦你对Pharo更熟悉),可以在不到一分钟的时间内轻松发现这一点:
#+ senders
为您提供选择器的发件人列表senders
的实现并通过可用的调用链(你可以用鼠标选择选择器或双击和ctrl + n来显示发件人浏览器)
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
答案 1 :(得分:4)
只是为了澄清@彼得的答案:
迭代
1 to: 10 do: [:each | dictionary at: each put: each]
每次迭代块时,将+ 1
发送到块变量each
。
我们在源代码中看不到+ 1
,但它实际上在幕后发生 。发现这种隐藏发送的原因是,Smalltalk不会分析源代码而是CompiledMethod
并通过查看文字框架来实现,并且正如Peter所说,也是在字节码上。
例如,通过编译和检查方法
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
。请注意,符号#+
不在文字框架中(检查器的“原始”选项卡)。它不在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:
并不重要,内联技术只是一种优化。在更高的抽象层次上,我们都希望看到我们的方法是一个&#34;发送者&#34; to:do:
,因此&#34;技巧&#34;将其添加到文字框架中。