SaveDefinitions被认为是危险的

时间:2011-07-05 08:13:10

标签: wolfram-mathematica mathematica-frontend

SaveDefinitionsManipulate的不错选择。它会导致Manipulate在Manipulate面板中存储用于创建的任何定义。以这种方式制作的Manipulate可以复制到空白笔记本中,并且仍然可以单独使用。此外,包含许多此类操作的工作笔记本也不会变成粉红色的盒子,打开时会在其下方打印错误消息。太好了!

然而,所有这些善良都有它的黑暗面,如果你不了解它,它会让你真的很难受。我已经在笔记本电脑中使用过几天了,但是我给你一个逐步的玩具示例场景,它重现了这个问题。

在这种情况下,你想要创建一个Manipulate来显示一个漂亮的波浪函数的图,所以你定义它(请创建一个这样的窗口大小,这很重要):

enter image description here

定义很好,所以我们下次保留它并使其成为初始化单元。接下来,我们添加Manipulate,并执行它。

f[x_] := x^2

Manipulate[
 Plot[n f[x], {x, -3, 3}],
 {n, 1, 4},
 SaveDefinitions -> True
 ]

一切都很棒,Manipulate真的很闪耀,这是美好的一天。

enter image description here

只是做你的偏执自我,检查定义是否正常:

enter image description here

是的,一切都还是检查出来。精细。但现在你想到一个更好的波浪函数是正弦,所以你改变定义,执行和偏执,检查:

enter image description here

一切都还好。你已经准备好从一天的努力中挽救你的工作并退出。 [退出内核]

第二天。你重新开始工作了。您评估笔记本中的初始化单元格。定义还好吗?检查。

enter image description here

现在,您向下滚动到Manipulate框(由于SaveDefinitions而无需重新执行),使用滑块稍微玩一下。然后向上滚动。

enter image description here

作为偏执狂,你再次检查f:

的定义

enter image description here

瞧,有人改变了你背后的定义!根据In []数字(Information:def为f,In[1]首先?,In[2]秒,您的第一个和第二个In[3](?)检查之间没有执行任何操作? )。

发生什么事了?嗯,当然是ManipulateFullForm显示其内部结构:

Manipulate[Plot[n*f[x],{x, -3, 3}],{{n, 2.44}, 1, 4},Initialization:>{f[x_] := x^2}]

你有罪魁祸首。框的初始化部分再次定义f,但它是旧版本,因为我们在修改其定义后没有重新评估Manipulate。一旦操纵盒出现在屏幕上,它就会被评估,并且您已经恢复了原来的定义。全局!

当然,在这个玩具示例中,很快就会发现一些奇怪的事情。在我的情况下,我在一个更大的笔记本中有一个更大的模块,在经过一些调试后,我改变了一小部分。它似乎工作,但第二天,同样的错误,在再次击中之前曾经惹过我。我花了几个小时才意识到我用来研究各方面问题的几个操作中的一个是这样做的。

显然,我很想说,这是不受欢迎的行为。现在,对于强制性问题:我们可以做些什么来防止Manipulate的背后行为发生,而不是每次更改笔记本时重新执行笔记本中的每个Manipulate可能由他们使用的定义?

2 个答案:

答案 0 :(得分:10)

这是一次尝试。我们的想法是在您的操作代码中识别带有DownValues或其他...Values的符号,并使用唯一变量/符号代替它们自动重命名。在克隆符号功能的帮助下,这里的想法可以相当优雅地执行,我觉得这些功能不时有用。下面的函数clone将克隆给定的符号,生成具有相同全局定义的符号:

Clear[GlobalProperties];
GlobalProperties[] :=
  {OwnValues, DownValues, SubValues, UpValues, NValues, FormatValues, 
      Options, DefaultValues, Attributes};


Clear[unique];
unique[sym_] :=
 ToExpression[
    ToString[Unique[sym]] <> 
       StringReplace[StringJoin[ToString /@ Date[]], "." :> ""]];


Attributes[clone] = {HoldAll};
clone[s_Symbol, new_Symbol: Null] :=
  With[{clone = If[new === Null, unique[Unevaluated[s]], ClearAll[new]; new],
        sopts = Options[Unevaluated[s]]},
     With[{setProp = (#[clone] = (#[s] /. HoldPattern[s] :> clone)) &},
        Map[setProp, DeleteCases[GlobalProperties[], Options]];
        If[sopts =!= {}, Options[clone] = (sopts /. HoldPattern[s] :> clone)];
        HoldPattern[s] :> clone]]

如何实现函数本身有几种选择。一种是使用另一个名称引入该函数,使用与Manipulate相同的参数,比如说myManipulate。我将使用另一个:通过一些自定义包装器的Manipulate轻轻地重载UpValues,我将介绍。我会称之为CloneSymbols。这是代码:

ClearAll[CloneSymbols];
CloneSymbols /: 
Manipulate[args___,CloneSymbols[sd:(SaveDefinitions->True)],after:OptionsPattern[]]:=
   Unevaluated[Manipulate[args, sd, after]] /.
     Cases[
       Hold[args],
       s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> 
          clone[s],
       Infinity, Heads -> True];

以下是使用示例:

f[x_] := Sin[x];
g[x_] := x^2;

请注意,要使用新功能,必须在SaveDefinitions->True包装器中包装CloneSymbols选项:

Manipulate[Plot[ f[n g[x]], {x, -3, 3}], {n, 1, 4}, 
          CloneSymbols[SaveDefinitions -> True]]

Manipulate

这不会影响Manipulate内部代码中原始符号的定义,因为它们的克隆定义已保存并现在用于初始化。我们可以查看此FullForm的{​​{1}}来确认:

Manipulate

特别是,您可以将函数的定义更改为

Manipulate[Plot[f$37782011751740542578125[Times[n,g$37792011751740542587890[x]]],
   List[x,-3,3]],List[List[n,1.9849999999999999`],1,4],RuleDelayed[Initialization,
     List[SetDelayed[f$37782011751740542578125[Pattern[x,Blank[]]],Sin[x]],
       SetDelayed[g$37792011751740542587890[Pattern[x,Blank[]]],Power[x,2]]]]]

然后移动上面生成的f[x_]:=Cos[x]; g[x_]:=x; 的滑块,然后检查函数定义

Manipulate

?f Global`f f[x_]:=Cos[x] ?g Global`g g[x_]:=x 完全独立于任何内容,可以安全地复制和粘贴。这里会发生以下情况:我们首先找到包含非平凡ManipulateDownValuesSubValues的所有符号(也可以添加UpValues),然后使用{ {1}}和OwnValues即时创建克隆。然后,我们用Cases中的克隆替换所有克隆符号,然后让clone保存克隆的定义。通过这种方式,我们可以对所涉及的功能进行“快照”,但不会以任何方式影响原始功能。

克隆(符号)的唯一性已通过Manipulate函数解决。但请注意,尽管以这种方式获得的Manipulate - s不会威胁原始函数定义,但它们通常仍然依赖于它们,因此不能完全独立于任何事物而将它们视为独立。人们必须沿着依赖树走下去并克隆那里的所有符号,然后重建它们的相互依赖关系,在Manipulate中构建一个完全独立的“快照”。这是可行的,但更复杂。

修改

根据@Sjoerd的请求,当我们希望我们的unique - s更新到函数的更改时,我会为一个案例添加代码,但不希望它们主动干涉和更改任何全局定义。我建议使用“指针”技术的变体:我们将再次用新符号替换函数名称,但是,不是在我们的函数之后克隆那些新符号,我们将使用Manipulate的{​​{1}}选项简单地使这些符号成为我们函数的“指针”,例如Manipulate。显然,重新评估此类初始化代码不会损害ManipulateInitialization的定义,同时我们的Initialization:>{new1:=f,new2:=g} - s将对这些定义的更改做出响应。

首先想到的是我们可以简单地用新符号替换函数名,让f初始化自动完成剩下的工作。不幸的是,在该过程中,它遍历依赖树,因此,我们的函数的定义也将被包含 - 这是我们试图避免的。因此,我们将明确构造g选项。这是代码:

Manipulate

使用与之前相同的定义:

Manipulate

以下是Initialize生成的ClearAll[SavePointers]; SavePointers /: Manipulate[args___,SavePointers[sd :(SaveDefinitions->True)], after:OptionsPattern[]] := Module[{init}, With[{ptrrules = Cases[Hold[args], s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> With[{pointer = unique[Unevaluated[s]]}, pointer := s; HoldPattern[s] :> pointer], Infinity, Heads -> True]}, Hold[ptrrules] /. (Verbatim[HoldPattern][lhs_] :> rhs_ ) :> (rhs := lhs) /. Hold[defs_] :> ReleaseHold[ Hold[Manipulate[args, Initialization :> init, after]] /. ptrrules /. init :> defs]]]

ClearAll[f, g];
f[x_] := Sin[x];
g[x_] := x^2;

新生成的符号充当我们函数的“指针”。使用这种方法构造的FullForm - 将响应我们函数的更新,同时对主函数的定义无害。付出的代价是它们不是自包含的,如果主要功能未定义则无法正确显示。因此,可以使用Manipulate包装器或In[454]:= FullForm[Manipulate[Plot[f[n g[x]],{x,-3,3}],{n,1,4}, SavePointers[SaveDefinitions->True]]] Out[454]//FullForm= Manipulate[Plot[f$3653201175165770507872[Times[n,g$3654201175165770608016[x]]], List[x,-3,3]],List[n,1,4],RuleDelayed[Initialization, List[SetDelayed[f$3653201175165770507872,f],SetDelayed[g$3654201175165770608016,g]]]] ,具体取决于所需的内容。

答案 1 :(得分:5)

答案是使用初始化单元作为Manipulate的初始化:

Manipulate[
 Plot[n f[x], {x, -3, 3}], {n, 1, 4}, 
 Initialization :> FrontEndTokenExecute["EvaluateInitialization"]]

您还可以使用DynamicModule

DynamicModule[{f},
 f[x_] := x^2;
 Manipulate[Plot[n f[x], {x, -3, 3}], {n, 1, 4}]]

在这种情况下,您不需要SaveDefinitions -> True

修改

回应Sjoerd的评论。使用以下简单技术,您无需在任何地方复制定义并在更改定义时更新所有副本(但您仍需要重新评估代码以获得更新Manipulate):

DynamicModule[{f}, f[x_] := x^2;
  list = Manipulate[Plot[n^# f[x], {x, -3, 3}], {n, 2, 4}] & /@ Range[3]];
list // Row