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