2011/10/5 Michael Roberts <[email protected]>:
> I find the thread a bit confused in the sense
>
> Dictionary *new* at: x ifAbsentPut: y
>
> does not make sense (is academic), because the new dictionary will
> never have x as a key. So why profile it and use that as reasoning?...
>
> whereas
>
> myDict at:x ifAbsentPut: y
>
> is more interesting.
>
I guess in some examples, like when you want to serialize an object
graph with as many nodes as branches (no or few sharing), then the
case of absence is dominating.
Knowing that Mariano is working on Fuel, i guess that could have
biased his judgement.
> In my experience i am biased by the use of the message in the systems
> that already use it heavily...however it always feels that the use of
> ifAbsentXX is intentionally deferring some code because it expresses
> exceptional cases in the application logic. Especially if the block
> makes an object you don't want to be doing that every time and
> discarding the result irrespective of the presence of the key. It does
> not have to be expensive to make, just not good style near any kind of
> loop.
>
> I know some people that actually don't like the ifAbsentPut: []
> variant. and prefer to be explicit, of the form
>
> (myDict includesKey: x) ifFalse: [
> myDict at: x put: y]
>
Both codes are valid. But note that at:ifAbsentPut: will return the
value, or the newly put value.
So the exact equivalent is ((myDict includesKey: x)
ifTrue: [myDict at: x]
ifFalse: [myDict at: x put: y])
However, maybe Mariano is searching for tight inner loop optimization
(we can only guess, because he didn't tell, he should have).
In this case, using ifFalse: is good: because it is inlined by
Compiler, it avoids a BlockClosure creation.
But above code will cost two lookup in both branch, so it will be
pretty bad too, depending on hash evaluation cost and collision rate,
in a majority of cases, worse than block creation time.
I would suggest to Mariano to implement in the Dictionary class of his
choice the single lookup, block free creation code:
at: key ifAbsentPutValue: aValue
"Answer the value associated with the key.
if key isn't found, associate it first with aValue."
| index |
^((array at: (index := self scanFor: key))
ifNil: [ array at: index put: (key -> aValue). aValue ]
ifNotNil: [:value | value]
But, please, please, Mariano, don't change #at:ifAbsentPut:
As other said, conditional execution is useful both for speed and
because in Object world, the state can change.
aBagOfKeys do: [:key | myDict at: key ifAbsentPut: (myStream next)].
aBagOfKeys do: [:key | myDict at: key ifAbsentPut: [myStream next]].
won't behave the same if aBagOfKeys has duplicates, will they ?
Last thing, #at:ifAbsentPut: is absolutely decoupled from
Object>>#value, it does rely on Object>>#value
So don't throw #at:ifAbsentPut: because you don't like Object>>#value.
If you don't like it, don't use it, use a BlockClosure instead.
Anyway, thanks to Mariano for these questions, they were biased, but
not silly, no question is :)
Nicolas
> where y is either a simple value or some expression.
>
> here the test on the key has the same effect as expecting a block in
> the inline ifAbsentPut: [] . It makes both forms basically the same,
> with one being more compact if you prefer that. If ifAbsentPut: did
> not expect the block then i think it would be a bit odd because you
> would have to unroll the code in order to achieve the deferred case
> which i feel is more common.
>
> cheers,
> Mike
>
> On Tue, Oct 4, 2011 at 2:44 PM, Henrik Sperre Johansen
> <[email protected]> wrote:
>> On 04.10.2011 12:52, Mariano Martinez Peck wrote:
>>
>> Hi guys. If I tell you the selector is Dictionary >> #at:ifAbsentPut: what
>> would you expect the second parameter to be? the value.
>> So, one would do:
>> Dictionary new at: #foo ifAbsentPut: 4
>>
>> But if you see Dictionary >>
>>
>> at: key ifAbsentPut: aBlock
>> "Return the value at the given key.
>> If key is not included in the receiver store the result
>> of evaluating aBlock as new value."
>>
>> ^ self at: key ifAbsent: [self at: key put: aBlock value]
>>
>> so it expects a Block. Ok, we are in Smalltalk, so implementing #value is
>> enough.
>>
>> Well..the previous example works, but only because we have an ugly Object >>
>> value that returns self.
>> If I put instances of subclasses from ProtoObjects (proxies), that do not
>> work anymore.
>>
>> So...my question is we do Dictionary at: #foo put: 4, why #at:ifAbsentPut:
>> expects a block and not directly the value?
>>
>> Sven's reasons are correct I think, it's for efficiency, and elegant lazy
>> initialization of a cache.
>> The same reasons will never hold true when using #at:put:.
>>
>> in which case I need a block instead of the value object directly ?
>>
>> In addition to when you want delayed computation, when you have value
>> objects who redefine/do not define #value ;)
>>
>> Cheers,
>> Henry
>>
>
>