# Saving function evaluations to DownValues for function with options

I have a time-intensive function with options that I would like to not evaluate more than necessary.

Options[f] = {a -> aa, b -> bb};
f[opt : OptionsPattern[]] := f[opt] = (Print["eval"]; {OptionValue[a], OptionValue[b]})

f[a -> 1, b -> 2]
f[a -> 1, b -> 2]
f[b -> 2, a -> 1]

(* eval *)
(* {1, 2} *)
(* {1, 2} *)
(* eval *)
(* {1, 2} *)


As you can see, it evaluates again with the same options if they're in a different order. How best to deal with this?

The solution is to put the function inputs into a canonical form, suitable for use with a cache. For example:

Options[f] = { "a" -> 1, "b" -> 2}
f[x_, OptionsPattern[]] :=
Module[{a = OptionValue["a"], b = OptionValue["b"]},
internalF[{x, a, b}]
]

internalF[{x_, a_, b_}] := internalF[{x, a, b}] = ...


Use whatever canonicalization suites your input. For example, if your functions output is invariant to the ordering of an input list, sort the list.

That said, don't use DownValues for caching. That has always been a hack really, used because there were no good alternatives. It continues to be used because it is simple, but it has drawbacks, e.g. it cannot be cleared.

Instead, use an explicit cache. Since version 12.1, there is a data structure for this: LeastRecentlyUsedCache. For earlier version (down to 10.0) you can implement your own cache using associations, as I did in MaTeX: https://github.com/szhorvat/MaTeX/blob/master/MaTeX/MaTeX.m#L391

• It seems to clear just fine; I think that was fixed long ago. But is there anything more suitable than a fixed-size cache?
– Blrp
Jul 1 '21 at 10:03
• @Blrp I am not talking about any bug that may have been fixed, but the fact that you cannot control the size of the cache, and cannot clear the cache without completely clearing the function definition. That might not matter if you're hacking something together for personal use. A polished function, meant to be used by end-users, should not keep eating memory indefinitely. Jul 1 '21 at 10:54
• Oh, I thought you meant Clear[f] which IIRC used to not clear specific values. I had not thought about problematic memory growth - although a "fixed-memory cache" seems more suitable for that. I guess HashTable can be used where that's not an issue.
– Blrp
Jul 1 '21 at 11:09
• Is there a better way to get all option values than OptionValue@Options[f][[;; , 1]]?
– Blrp
Jul 2 '21 at 23:51