2
$\begingroup$

I want to define a symbolic-valued function using Set to memoize the result. However, I get different results when using an Association and a List:

f1[x_] = (Pause[3];{"a"->1,"b"->x}) f2[x_] := (Pause[3];{"a"->1,"b"->x}) g1[x_] = (Pause[3];Association@@{"a"->1,"b"->x}) g2[x_] := (Pause[3];Association@@{"a"->1,"b"->x}) 

When running these functions, only f1 and f2 produce the correct result, and only f1 memoizes the result, only requiring one computation involving a Pause (here standing in for a more expensive computation).

In[2]:= f1[y] Out[2]= {"a" -> 1, "b" -> y} In[3]:= f2[y] Out[3]= {"a" -> 1, "b" -> y} (* long computation *) In[4]:= g1[y] Out[4]= <|"a" -> 1, "b" -> x|> (* incorrect *) In[5]:= g1[y] Out[5]= <|"a" -> 1, "b" -> x|> (* incorrect; long computation *) 

Looking at the common pitfalls question, one finds that Associations were atomic in Mathematica™ 10.4. I am currently using version 13, and it appears that AtomQ@Association == True still. I suspect this is causing the issue I am working with.

As Mathematica™'s built-in dictionary data structure, I first attempted to use it to build a class-like object one can build easily in other languages. Building getters and setters with Associations is relatively easy, but now function definitions are getting complicated.

Am I better off using lists of rules to get around this issue, or is there a workaround I can use, perhaps with Replace[expr,x->#]& or something to that effect?

$\endgroup$
3
  • $\begingroup$ It is simply bad practice to define functions using Set instead of SetDelayed - end of story. Use g2 as you have written it, not g1. $\endgroup$ Commented Nov 15, 2022 at 19:06
  • $\begingroup$ I think that last was meant to be g2[y] and not a repeat of g1[y]. $\endgroup$ Commented Nov 15, 2022 at 19:30
  • $\begingroup$ @Phro - you haven't made it clear what the actual problem you are trying to solve is, and so you have users guessing and giving answers. $\endgroup$ Commented Nov 15, 2022 at 20:01

2 Answers 2

3
$\begingroup$

I think you're running into the issue described here. To work around it, you can try something like the following:

(g[x_] := Association[#]) &[ (Pause[3]; {"a" -> 1, "b" -> x}) ] g[2] (* <|"a" -> 1, "b" -> 2|> *) 

Effectively, we are pre-computing the result without the Association wrapper, and create a SetDelayed rule that only converts it into an association before returning. This keeps the x visible to SetDelayed, while still pre-computing everything else.

An alternative that also supports nested associations is to use a dummy symbol:

(g[x_] := With[{association = Association}, #]) &[ (Pause[3]; association["a" -> 1, "b" -> x, "c" -> association["d" -> x^2]]) ] g[2] (* <|"a" -> 1, "b" -> 2, "c" -> <|"d" -> 4|>|> *) 

Here, we replace the dummy symbol association with Association before returning the result. I used With above, but ReplaceAll and similar should also work

$\endgroup$
4
  • $\begingroup$ How is this different from g[x_] := Association["a" -> 1, "b" -> x]? I don't understand what problem this solves $\endgroup$ Commented Nov 15, 2022 at 19:22
  • $\begingroup$ @JasonB. I interpreted the question as being about the case where {"a" -> 1, "b" -> x} (i.e. the content of the association) is slow to compute, hence the attempt in the question to use Set instead of SetDelayed - I might be wrong though $\endgroup$ Commented Nov 15, 2022 at 19:40
  • $\begingroup$ This addresses my question, but not quite my use-case, which involves nested associations. (say ..."b"-><|"b1"->x, "b2"->x^2|>...|>). Is it better to post this follow-up as a separate question? $\endgroup$ Commented Nov 15, 2022 at 19:46
  • $\begingroup$ @Phro See the update $\endgroup$ Commented Nov 15, 2022 at 20:02
0
$\begingroup$

I don't actually see any memoization going on here. To get it memoized, the typical pattern looks like this:

g11[x_] := g11[x] = <|"a" -> 1, "b" -> x|> 

or to mimic your complex calculation

g11[x_] := (Pause[3]; g11[x] = <|"a" -> 1, "b" -> x|>) 

Does that work for you?

$\endgroup$
3
  • 1
    $\begingroup$ The memoization I am looking for is not about caching the values of the function for its various inputs, but caching the expression (itself a function of the inputs) so future substitution runs quickly. $\endgroup$ Commented Nov 15, 2022 at 19:43
  • $\begingroup$ Hmm. Then why don't you just pre-compute it? Since the example you've given shows no dependency between the actual function definition and this expensive precomputation, it's difficult to understand what the problem is here. $\endgroup$ Commented Nov 15, 2022 at 20:00
  • $\begingroup$ In practice, the computation is indeed coupled with the input variables, though only so far as playing the role of indices. In this way, I wish to run the computation to end up with a function whose only role is to substitute in the values of said indices. $\endgroup$ Commented Nov 15, 2022 at 21:26

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.