A generic Apps Script memoization function can be written to cache any function. There are two parts to this functionality:
- Generate a unique key for the function and arguments
- Cache the result of the function using the
CacheService
Generating a unique key
Below is a generic hash function that takes a string and computes a hash using the specified algorithm. The default algorithm is MD5, but can be changed to any of the Utilities.DigestAlgorithm values.
/**
* A generic hash function that takes a string and computes a hash using the
* specified algorithm.
*
* @param {string} str - The string to hash.
* @param {Utilities.DigestAlgorithm} algorithm - The algorithm to use to
* compute the hash. Defaults to MD5.
* @returns {string} The base64 encoded hash of the string.
*/
function hash(str, algorithm = Utilities.DigestAlgorithm.MD5) {
const digest = Utilities.computeDigest(algorithm, str);
return Utilities.base64Encode(digest);
}
An example output of this function is:
hash("test"); // "CY9rzUYh03PK3k6DJie09g==" The key for the memoization function will be the hash of the function name and arguments. The function name is included to prevent collisions between functions with the same arguments. The arguments are stringified to allow for any type of argument to be passed to the memoized function.
const key = hash(JSON.stringify([func.toString(), ...args])); ❗ JSON.stringify will not work for all types of arguments such as functions, dates, and regex. These types of arguments will need to be handled separately.
Caching the result
The memoization function will first check the cache for the key. If the key exists, the cached value will be returned. If the key does not exist, the function will be called and the result will be cached.
/**
* Memoizes a function by caching its results based on the arguments passed.
*
* @param {Function} func - The function to be memoized.
* @param {number} [ttl=600] - The time to live in seconds for the cached
* result. The maximum value is 600.
* @param {Cache} [cache=CacheService.getScriptCache()] - The cache to store the
* memoized results.
* @returns {Function} - The memoized function.
*
* @example
*
* const cached = memoize(myFunction);
* cached(1, 2, 3); // The result will be cached
* cached(1, 2, 3); // The cached result will be returned
* cached(4, 5, 6); // A new result will be calculated and cached
*/
function memoize(func, ttl = 600, cache = CacheService.getScriptCache()) {
return (...args) => {
// consider a more robust input to the hash function to handler complex
// types such as functions, dates, and regex
const key = hash(JSON.stringify([func.toString(), ...args]));
const cached = cache.get(key);
if (cached != null) {
return JSON.parse(cached);
} else {
const result = func(...args);
cache.put(key, JSON.stringify(result), ttl);
return result;
}
};
}
Limitations
There are some limitations to be aware of when using the CacheService:
- The maximum size of a cached value is 100KB
- The maximum key length is 250 characters
- The maximum number of cached items is 1000.
- Only strings can be stored in the cache. Objects must be stringified before being stored.
Read more about these limitations at CacheService.put().
Frequently Asked Questions
What is memoization in Apps Script?
I think of memoization as a way to cache the results of a function. In Apps Script, I can write a generic memoization function that takes any function, generates a unique key for it and its arguments, and then uses the CacheService to store the result.
How do you generate a unique key for a memoized function?
To generate a unique key, I create a hash from the function's code (by calling func.toString()) and its arguments (which I stringify). This way, the same function with the same arguments will always have the same key.
How does the memoization function work?
My memoization function is pretty simple. It first checks the cache to see if a key exists. If it does, I just return the cached value. If not, I call the original function, store its result in the cache with the key, and then return the result.
What are the limitations of using CacheService for memoization?
When I use CacheService for memoization, I have to keep its limitations in mind. The big ones are: a max value size of 100KB, a max key length of 250 characters, and a max of 1000 items in the cache. Also, CacheService only stores strings, so I have to remember to stringify any objects.
© 2023 by Justin Poehnelt is licensed under CC BY-SA 4.0