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$
    – Jason B.
    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$ 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$
    – Jason B.
    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$
    – Jason B.
    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$
    – Lukas Lang
    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$
    – Phro
    Nov 15, 2022 at 19:46
  • $\begingroup$ @Phro See the update $\endgroup$
    – Lukas Lang
    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$
    – Phro
    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$
    – lericr
    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$
    – Phro
    Nov 15, 2022 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.