# The dangers of SaveDefinitions — should this really happen?

I have been bitten hard by SaveDefinitions -> True. I'll describe in detail what happened below.

My questions are: Is this a bug? What is the most convenient workaround?

Consider a definition issued like this:

Block[{x, y}, f[x_, y_] = x + y;]


Why didn't I use := instead? Because the expression that stands in place of x+y in my actual problem is computed (symbolically) within the Block so (numerical) evaluations of f are going to be sufficiently fast.

We can check the definition:

?f


f[x_,y_]=x+y

Now let's give x a value ...

x = 1


... and test that f still works as expected:

f[0, 0]


0 (* as expected *)

Let's use f in Manipulate with SaveDefinitions -> True ...

Manipulate[f[a, b], {a, -1, 1}, {b, -1, 1}, SaveDefinitions -> True]


... and check that it works again:

f[0, 0]


1 (* oops!! *)

?f

f[x_, y_] = 1 + y


The definition of f has been rewritten and changed to something else as a side effect of SaveDefinitions.

What is the morale? Probably that SaveDefinitions and Set are not safe to use together.

Note that what happened here is different form the situation when the definition of f is overwritten just because a notebook containing a manipulate with SaveDefinitions has been opened.

My current workaround is to use the following hack to "neutralize" the HoldAll attribute of SetDelayed:

Block[{x, y},
(f[x_, y_] := #) &[x + y];]


Alternative suggestions are welcome.

-
I have the feeling it won't get much better than using Evaluate or your alternative – Rojo Sep 9 '13 at 19:51
Didn't I tell you before that SaveDefinitions is dangerous? – Sjoerd C. de Vries Sep 9 '13 at 21:11
Although this is posed as problem with Manipulate, hasn't this behavior been a problem with Save for a much longer time? I suspect that the dependency tracing mechanism used in Save is just showing up again. – m_goldberg Sep 9 '13 at 23:21
I never use SaveDefinitions. Just never. It's terribly convenient, but for my purposes, it is just not sufficiently predictable. And it can sometimes be incredibly inefficient (e.g., when it stores ridiculous amounts of definitions which were hidden behind a Needs or Get). – John Fultz Sep 12 '13 at 6:02
@Szabolcs All SaveDefinitions does is to auto-construct an Initialization option for you. I'm a control freak. Let me construct my own Initialization option. – John Fultz Sep 13 '13 at 19:52

Using InputForm on your Manipulate reveals (like it did in my SaveDefinitions Considered Dangerous post) that it contains the following:

Manipulate[f[a, b], {a, -1, 1}, {b, -1, 1}, Initialization :> {f[x_, y_] = x + y, x = 1}]


So, it actually stores two definitions, one for f and one for x. This actually makes sense, as SaveDefinitions's task is to store any definitions that the Manipulate depends on. When it is checking for this, it finds f, stores its definition and probably then checks whether fitself has any dependencies that need to be taken into account.It checks x and finds a definition in the Global namespace, so this is stored as well.

-
@kuba ... or just use \[FormalX] and \[FormalY] symbols in your definitions. Can't go wrong with that. – Sjoerd C. de Vries Sep 9 '13 at 21:50
Oh, I wasn't aware of this. Thanks :) – Kuba Sep 9 '13 at 21:52
More people should use the InputForm technique. Bravo to you. – John Fultz Sep 12 '13 at 6:04
@JohnFultz Thanks! – Sjoerd C. de Vries Sep 12 '13 at 10:43

This seems to be a very easy way to bite yourself in the foot (non-flexible programmers this is not for you). I also have the habit of doing those blocked set-based definitions. It's probably time to change the habit now.

It seems to me that SaveDefinition extracts the definitions of the symbols required by the Manipulate, as you entered them. If you used :=, then it will be stored as :=, if = then =. So, if you made a definition with = that required certain localization, that localization won't be captured by SaveDefinitions.

I am not sure how to avoid that, other than using := in the first place and ensuring what you pass to := is already the final form of the rhs you desire.

So, your own suggestion looks good. The natural alternative is using f[x_, y_]:=Evaluate[...].

Another attempt of an alternative could be to modify your Manipulate after creation, changing the Set-based definitions to SetDelayed (assuming this doesn't bring other unintended sideeffects).

An implementation could be (please @Mr.Wizard, prettify this if you see a nice way. I haven't got the time or brains these days)

postProcessTheManipulate[m_Manipulate] :=
Replace[m // ToBoxes // ToExpression, (Initialization :> l_List) :>
Block[{},
(Replace[Hold@l,
HoldPattern@
Set[args : PatternSequence[Except[_Symbol], ___]] :>
SetDelayed[args], {2}] /.
Hold[ll_] :> (Initialization :> ll)) /; True], {1}]


To be used

Manipulate[f[a, b], {a, -1, 1}, {b, -1, 1},
SaveDefinitions -> True] // postProcessTheManipulate


Edit by Mr.Wizard -- As requested here is a terse version, assuming it is safe to replace all Set expressions at level one on the RHS of Initialization:

pptm[m_Manipulate] :=
Replace[m // ToBoxes // ToExpression,
init : (Initialization :> _) :>
RuleCondition @ Replace[init, Verbatim[Set][x__] :> SetDelayed[x], {2}],
{1}
]

-
Bite yourself in the foot? Around here we say shoot yourself in the foot, which somehow makes a lot more sense. :^) – Mr.Wizard Sep 9 '13 at 20:21
Could you explain the reason for args : PatternSequence[Except[_Symbol], ___] -- at the moment I don't see the need for the complex pattern. – Mr.Wizard Sep 9 '13 at 20:26
@Mr.Wizard I was lazy to think whether there was a statistically significant disadvantage to changing ALL = to := so I just tried to prevent the changing in the obvious Set's kingdom: the x=something;-land – Rojo Sep 9 '13 at 20:28
By providing the Manipulate with Method -> {"ExtraVariables" :> {f}} f will get localized and the original un-delayed definition wont be contaminated. ( {f,x}` in Mr.Wizards version ) – ssch Sep 9 '13 at 20:40
I think it should be safe, so for the sake of shorter code I left it out. Now people have options. – Mr.Wizard Sep 9 '13 at 20:41