I was discussing with a colleague the types of keywords we surround blocks with. In particular not which message we send to them, but which types of keywords frequently get passed literal blocks as arguments. After we had asserted what we were sure was the "obvious common pattern", my interest was piqued. I sat down and wrote some code.
First I flirted with ParseTreeSearcher. This seems like an obvious thing. But it's not the right way (IMO) to do this kind of query. The ParseTreeSearcher is about matching relatively simple patterns against source trees. It uses a custom language for specifying the patterns to match.
Luckily, we're not beholden to use a ProgramNodeVistor just so we can enumerate with code that looks like someExpression ifTrue: [ noteIt ]. ProgramNode responds to nodesDo:. The query, in pseudo code, which I'm interested goes something like this:
- is it a block?
- does the block's argument count match the kinds of blocks I'm looking for (e.g. niladic, etc)?
- is the block an argument to a keyword message?
- what subpart of the selector preceded the block then?
Here's a chunk of code that sweeps my whole development image for that:
bag := Bag new.
blockArgCount := 0.
SystemUtils allBehaviorsDo: [:eachBehavior |
eachBehavior selectorsAndMethodsDo: [:selector :ignored |
(eachBehavior parseTreeFor: sel)
ifNotNil: [:tree |
tree nodesDo: [:each |
"is it a block?"
each isBlock
ifTrue: ["is it the arg count I'm interested in?"
each arguments size = blockArgCount
ifTrue: ["is it an argument to a message?"
(each parent isMessage and: [each parent receiver ~~ each])
ifTrue: ["then record the keyword that preceded it"
bag
add: (each parent selectorParts at: (each parent arguments indexOf: each)) value]]]]]]].
top15 := ((bag contents associations
asSortedCollection: [:a :b | a value > b value]) first: 15)
collect: [:each | each key -> (each value asFloat / bag size)]
Here's the results for blocks of argument count 0, 1, and 2.
0 Arguments
Selector | Percent |
ifTrue: | 46.8734 |
ifFalse: | 28.023 |
and: | 7.13361 |
or: | 4.30638 |
ifAbsent: | 2.50518 |
ifNil: | 1.08428 |
whileTrue: | 0.860953 |
ensure: | 0.825349 |
ifNone: | 0.792983 |
whileFalse: | 0.74767 |
ifAbsentPut: | 0.66999 |
ifNotNil: | 0.589073 |
showWhile: | 0.390018 |
should: | 0.351178 |
timesRepeat: | 0.309101 |
1 Argument
Selector | Percent |
do: | 56.2116 |
collect: | 9.81889 |
select: | 4.85706 |
ifNotNil: | 3.74195 |
detect: | 3.52492 |
anySatisfy: | 1.81111 |
reject: | 1.55665 |
ifDefinedDo: | 1.42194 |
contains: | 0.935489 |
allSatisfy: | 0.935489 |
getBlock: | 0.898069 |
reverseDo: | 0.666068 |
compute: | 0.51639 |
allBehaviorsDo: | 0.434067 |
bindingsDo: | 0.389163 |
2 Arguments
Selector | Percent |
keysAndValuesDo: | 20.0799 |
do: | 16.7832 |
into: | 9.99001 |
asSortedCollection: | 8.84116 |
putBlock: | 5.74426 |
put: | 4.74525 |
selectorsAndMethodsDo: | 3.24675 |
sortBlock: | 2.74725 |
onError: | 1.4985 |
block: | 1.34865 |
useTempClass: | 1.2987 |
fold: | 1.14885 |
visualBlock: | 1.0989 |
sorted: | 1.04895 |
filterBlock: | 0.999001 |