这是一个尝试。这个想法是在您的操纵代码中识别符号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
,因为它是它们的克隆,其定义已被保存并现在用于初始化。我们可以查看一下FullForm
以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
这Manipulate
合理地独立于任何东西,并且可以安全地复制和粘贴。这里发生的情况如下:我们首先找到所有带有 non-trivial DownValues
, SubValues
or的符号UpValues
(也可以添加OwnValues
),然后使用Cases
和clone
动态创建它们的克隆。然后我们用它们在里面的克隆来替换所有克隆的符号Manipulate
,然后让我们Manipulate
保存克隆的定义。通过这种方式,我们对所涉及的功能进行了“快照”,但不会以任何方式影响原始功能。
该功能已解决了克隆(符号)的唯一性unique
。但是请注意,虽然Manipulate
以这种方式获得的 -s 不会威胁到原始函数定义,但它们通常仍将依赖于它们,因此不能认为它们完全独立于任何东西。必须沿着依赖关系树向下移动并克隆那里的所有符号,然后重建它们的相互依赖关系,以在 Manipulate 中构建一个完全独立的“快照”。这是可行的,但更复杂。
编辑
根据@Sjoerd 的请求,当我们确实希望我们的Manipulate
-s 更新函数的更改但不希望它们主动干扰和更改任何全局定义时,我添加了代码。我建议一种“指针”技术的变体:我们将再次用新符号替换函数名称,但是,我们将使用Manipulate
'Initialization
选项简单地将这些符号“指针”到我们的函数之后,而不是在我们的函数之后克隆这些新符号。函数,例如Initialization:>{new1:=f,new2:=g}
. 显然,重新评估此类初始化代码不会损害f
or的定义g
,同时我们的Manipulate
-s 将响应这些定义的变化。
第一个想法是我们可以简单地用新符号替换函数名,然后让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]]]]
新生成的符号充当我们函数的“指针”。用这种方法构造的Manipulate
-s 将响应我们函数中的更新,同时对主要函数的定义无害。付出的代价是它们不是独立的,如果主要功能未定义,将无法正确显示。因此,可以使用CloneSymbols
wrapper 或SavePointers
,这取决于需要什么。