I came up with a construct that works well enough for my current use case but has some flaws (at least I suspect it does). Say we have a function that calculates some stuff that takes a while to process (nonlinear constrained optimisation in my case) and has some primary output (e.g. best fit parameters) but some secondary outputs that might be interesting in themselves or for diagnostics as well (e.g. a plot of the residual errors). Returning an Association
containing the different outputs and thus making them accessible seems a good approach to me.
Now let's say some of those outputs are computationally expensive and are not generally needed when all goes well and the primary result looks fine right out of the box. In that case we would like to avoid the cost of calculating the secondary output(s), but being able to generate them at will would be nice nonetheless. The construct I used is:
ClearAll@func;
func[a_] := Module[{primaryOutput, secondaryOutput},
primaryOutput = (Pause@5; 2 a);
secondaryOutput := (Pause@2; 0.5 a);
Association["1" -> primaryOutput, "2" :> secondaryOutput]]
Please note the use of RuleDelayed
and SetDelayed
insides Module
and let's test what happens when we call that thing.
(test = func[1]) // AbsoluteTiming
(* {5.00136, <|"1" -> 2, "2" :> secondaryOutput$28323|>} *)
test["1"] // AbsoluteTiming
(* {3.*10^-6, 2} *)
test["2"] // AbsoluteTiming
(* {2.00089, 0.5} *)
As we see from the timings, all looks good in principe. The general call of the function takes approx. five seconds (which is the duration of our primary "calculation") and no time is spend on the optional calculation of the secondary output. Accessing the result (with the "1"
-key) is instantaneous. Accessing the secondary output (with the "2"
-key) takes approx. two seconds (instead of seven) thus unnecessary recalculation of the primary output is avoided.
What I suspect might be an issue here is the secondaryOutput$28323
returned inside the resulting association. A new module variable is create at each call of func
and I wonder both about proper scoping and garbage collection of these module variables. So the question is wether there is some merit to my suspicion and if so, how to fix this.
Bonus question: Since calculating the secondary output is expensive one might like to cache it using memoisation. Does this worsen the possible issue with those module variables?
Temporary
-attribute. $\endgroup$Module
assignsTemporary
attribute to them automatically. They are collected after no longer referenced, this is also automatic. The only cases I know of, which are problematic, I discussed in this answer. So, basically, if your use case does not involve any of those subtle cases, just use thoseModule
variables and don't worry about GC (don't forget set$HistoryLength
to zero when working in the FrontEnd). $\endgroup$Temporary
fault but a "conservative" approach works stable: mathematica.stackexchange.com/q/116117/5478 $\endgroup$Temporary
attributes (although I may well be wrong). Something to look into, when I get a spare moment. But normally,Module
-generated variables captured by UI elements are simply considered referenced from the GC, and work just fine within a single Mathematica session - at least this has been my experience. $\endgroup$