[Cuis-dev] memoizing a method
Mark Volkmann
r.mark.volkmann at gmail.com
Fri Dec 6 04:23:12 PST 2024
Thanks for the feedback! I added this class method in Object so I can clear
the memoized values for a given class and/or method:
clearMemo: aString
"Delete all memoized entries from Smalltalk Dictionary whose keys
begin with 'memo-' followed by a given string."
| prefix |
prefix := 'memo-', aString.
Smalltalk keys do: [ :key |
key isSymbol and: [key asString beginsWith: prefix] :: ifTrue: [
Smalltalk removeKey: key ].
].
Of course we can still run out of memory if this is not used at some point
and the results of a large number of argument combinations get cached.
I changed the key prefix from 'cache-' to 'memo-' to more clearly
associate them with the memoize method.
The key I'm using does capture the class, selector, and the arguments, but
not a specific receiver object. Here's an example key for calls to the
class method indentDepth in the class ParseTree: #'memo-ParseTree
class>>indentDepth:'
Would it be more appropriate to add the memoize: and clearMemo: methods to
the Behavior class instead of the Object class? Perhaps the answer is that
they shouldn't be added to either of those.
On Fri, Dec 6, 2024 at 12:39 AM Luciano Notarfrancesco <luchiano at gmail.com>
wrote:
> It’s an interesting idea, but this implementation could cause problems.
> For example, the cache only grows, and over time you run out of memory. And
> the symbol you create should uniquely identify with the message (receiver,
> selector, arguments), maybe you should use the message itself as key
> instead? But even considering those things, I’m not sure this is a
> practical idea that I’d use in my code.
>
> Have you used a Smalltalk with support for instance-specific methods? I
> have some objects that are immutable in some sense (equality is invariant
> over the instances lifetime, but they can change state as long as it
> doesn’t break that invariant, for example to cache computations, or to do
> lazy initialization and let some data be computed only when needed). These
> objects often implement some unary messages that are “pure functions”, i.e.
> they will always compute the same value for a given instance, so they could
> be cached. I normally cache them in a properties dictionary that each
> instance has, sometimes in an instance variable, but if we had
> instance-specific methods I could just use that. For example, say you want
> to do this for the average of #(1 2 3), then you implement the method
> >>#average so that at the end, before returning the computed value, adds a
> new instance method #average to the receiver that stores that value as a
> literal and just returns that value, i.e. a method that decompiled would
> look like this:
>
> average
> ^ 2
>
> The next time you send #average to #(1 2 3) it it will just return the
> stored value, only for the instance #(1 2 3).
>
> I don’t think there’s any plan to support this in Cuis or the
> OpenSmalltalk VM, tho.
>
> Cheers,
> Luciano
>
>
> On Fri, Dec 6, 2024 at 04:24 Mark Volkmann via Cuis-dev <
> cuis-dev at lists.cuis.st> wrote:
>
>> Certainly it's not common to want to memoize a method, but I was
>> wondering if it would be easy to do. I came up with the following that
>> works well. I'd be interested in hearing whether there is a better approach
>> and whether something like this is already in the base image and I just
>> haven't found it yet.
>>
>> I added the Object instance method memoize: which takes a block.
>> This caches previously computed values in an IdentityDictionary
>> that is saved in Smalltalk which is also an IdentityDictionary.
>>
>> memoize: aBlock
>> | cache cacheKey sender valueKey |
>>
>> sender := thisContext sender.
>>
>> "Smalltalk is a SystemDictionary which is an IdentityDictionary.
>> That is why cacheKey must be a Symbol."
>> cacheKey := ('cache-', sender name) asSymbol.
>>
>> cache := Smalltalk at: cacheKey ifAbsentPut: [ IdentityDictionary new
>> ].
>> valueKey := thisContext name, sender arguments asString :: asSymbol.
>>
>> ^ cache at: valueKey ifAbsentPut: [ aBlock value ].
>>
>> Using the memoize: method is demonstrated in the class method average:
>> below
>> (added to any class) which takes an array of numbers.
>>
>> average: numberArray
>> ^ self memoize: [
>> | sum |
>> 'computing average' print.
>> sum := numberArray fold: [:acc :n | acc + n].
>> sum / numberArray size.
>> ].
>>
>>
>> --
>> R. Mark Volkmann
>> Object Computing, Inc.
>> --
>> Cuis-dev mailing list
>> Cuis-dev at lists.cuis.st
>> https://lists.cuis.st/mailman/listinfo/cuis-dev
>>
>
--
R. Mark Volkmann
Object Computing, Inc.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.cuis.st/mailman/archives/cuis-dev/attachments/20241206/b66d7c19/attachment-0001.htm>
More information about the Cuis-dev
mailing list