A good way to cache expression results is to implement compiler optimizations such as common subexpression elimination, and lifting of invariant expressions. The net result is that during runtime, there are fewer evaluations of an expression that returns the same result each time. This would be my recommendation.
Another way to cache expression results might be, at runtime, to hash a function name with its arguments and do a lookup in a hash table to see if the result has already been computed. But, this doesn't decrease the number of times we attempt to evaluate the expression, and it adds additional runtime overhead to the evaluation of the expression. So it is less likely to be a win.
Haskell and some other functional languages use lazy evaluation. An expression is compiled into a thunk, which is passed around at run time. The first time that the thunk is referenced in a value context, it is executed, and then the thunk object is overwritten with the resulting value. This approach also introduce a run-time overhead. It can speed up some programs, if a lot of expensive thunks never get evaluated, but it can slow down other programs.
OpenSCAD is not 100% referentially transparent. If a function F directly or indirectly calls `rands`, then calls to F aren't referentially transparent, and that inhibits the ability to do these optimizations. `rands` should be deprecated and replaced by a referentially transparent random number facility based on hashing.