2

(I'm not sure if the title of this question is appropriate, sorry!)

After defining selfApply function that resolves the mutual references contained in Association, I wrote the following code:

selfApply[x_Association] := x //. x;

u[a_] := Module[{f}, f[t_] := t^2; (f is actually intended to be non-Listable function )

selfApply@<|"x" -> 2 a, "y" -> (f /@ "x"), "z" -> "y"[[2]]|> ] u[Range[3]]

The result I expect is <|"x" -> {2, 4, 6}, "y" -> {4, 16, 36}, "z" -> 16|>, but the output is

Part::partd: Part specification y[[2]] is longer than depth of object.
<|"x" -> {2, 4, 6}, "y" -> {2, 4, 6}, "z" -> 4|>

with warning.

It is obvious that it stucks at the stage of evaluating f /@ "x" and "y"[[2]], so I tried to rewrite it as follows using RuleDelayed:

selfApply[x_Association] := x //. x;

u[a_] := Module[{f}, f[t_] := t^2;

selfApply@<|"x" -> 2 a, "y" :> f /@ "x", "z" :> "y"[[2]]|> ] v = u[Range[3]] v["y"] v["z"]

and got outputs:

<|"x" -> {2, 4, 6}, "y" :> f$729435 /@ {2, 4, 6},"z" :> (f$729435 /@ {2, 4, 6})[[2]]|>
{4, 16, 36}
16

The results of v["y"],v["z"] are just what I want, but this behavior is uncomfortable in the following ways:

  1. It appears that f is not evaluated when the result is assigned to v, but is evaluated when specifically calling v["y"]. Because of this, calculation results are unsure just by calling v. Also, if the processing of f is complicated, there is a concern that performance will be affected because f will run every time an element of v is accessed.

  2. The scope of f defined in Module is leaking, and the definitions multiply with each execution. Example:

s; s; s;
Names["Global`f$*"]

and output:

{"f$", "f$9454", "f$9455", "f$9456"}

The definitions of the form f$* increases each time s is executed.


My naive idea of using Evaluate also didn't work:Evaluate /@ u[Range[3]] or AssociationMap[Evaluate, u[Range[3]]].

I feel like my idea of ​​using RuleDelayed is possibly fundamentally wrong... Is there any elegant solution?

user14061
  • 93
  • 4
  • 1
    If you insist on selfApply (which I don't understand the motivation), //AssociationThread[Keys[#],Values[#]]& can evaluate the delayed values. – Lacia Nov 08 '23 at 02:49
  • The scope of f is not leaking - it's the designed behaviour. Every time Module is evaluated, the local vars will be generated to ensure the uniqueness. Why enclose f? You can simply hide it in some other context. – Lacia Nov 08 '23 at 02:51
  • I often use the strategy of selfApply when solving physical systems with a large number of parameters and their relationships mixed together. For example: selfApply@<|diam -> 1., dens -> 3., vel -> 2., area -> Pi (diam/2)^2, flux -> dens*vel*area|> , while I'm not sure if this way is common. – user14061 Nov 08 '23 at 03:53
  • Using AssociationThread looked trivial at first, but it seems to meet my needs successfully. Thank you! – user14061 Nov 08 '23 at 03:59
  • And yes, I know that local variables in a Module will be added to the global context. No matter how many times I run Module[{x}, x = 1]; Names["x*"], the result will be {x}, while the example I presented will multiply variables of the form f$* in the context each time it is run. This is what I'm concerned about since it can be interpreted as a kind of memory leak. – user14061 Nov 08 '23 at 04:13
  • The code may be somewhat unnatural as a result of simplifying to ask the question. A bit more realistic code: f[x_, a_] := Exp[a x]; u[a_] := Module[{s}, s = Mean[a]; selfApply@<|"x" -> a, "y" :> (f[#, s] & /@ "x"), "z" :> "y"[[2]]|> ]; u[Range[3]] – user14061 Nov 08 '23 at 04:34
  • Thanks for the explanation. I also don't know if there is a standard way of constructing associations that depend on itself. see e.g. the discussions here https://mathematica.stackexchange.com/q/278535/86893. Currently I use the most trivial method like Module[{vars},vars=...,<|keys->vars|>]. – Lacia Nov 09 '23 at 08:45
  • For the "leaking" of local variables, I believe it's due to nested scopings of Module and SetDelayed although I didn't test, see e.g. https://mathematica.stackexchange.com/questions/119403/ and related questions therein. I mean it's somewhat a common phenomenon but not well-documented. – Lacia Nov 09 '23 at 08:57

1 Answers1

2

Besides reconstruct the association via e.g. AssociationThread, you can also use the trick of in-place evaluation:

selfApply[x_Association] := x //. x;

u[a_] := Module[{f}, f[t_] := t^2; <|"x" -> 2 a, "y" :> f /@ "x", "z" :> "y"[[2]]|>//selfApply//Replace[#,x_:>RuleCondition@x,{1}]& ] v = u[Range[3]]

(<|"x" -> {2, 4, 6}, "y" :> {4, 16, 36}, "z" :> 16|>)

see e.g. https://mathematica.stackexchange.com/a/128456/86893. The undocumented RuleCondition (but quite stable across different versions) can also be replaced by documented functions, see e.g. https://mathematica.stackexchange.com/a/29318/86893

Lacia
  • 2,253
  • 3
  • 19