保存定义被认为是危险的 [英] SaveDefinitions considered dangerous

查看:90
本文介绍了保存定义被认为是危险的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

SaveDefinitionsManipulate的不错选择.它使Manipulate将用于创建它的任何定义存储在操纵"面板中.这样制作的机械手可以复制到空笔记本上,并且仍然可以单独工作.此外,包含许多此类操作的工作笔记本也不会变成一堆粉红色的盒子,打开后在其下方会显示错误消息.太好了!

SaveDefinitions is a nice option of Manipulate. It causes Manipulate to store any definitions used for its creation inside the Manipulate panel. A Manipulate made this way can be copied to an empty notebook and will still work on its own. Additionally, your working notebook containing many such Manipulates also doesn't turn into a flurry of pink boxes with printed error messages below it upon opening. Great!

但是,所有这些善良都有其黑暗的一面,如果您不了解,它可能会给您带来极大的痛苦.我已经在工作了几天的笔记本中遇到了这个问题,但是我向您展示了一个重现玩具问题的分步教学示例场景.

However, all this goodness has its dark side which can bite you real hard if you are not aware of it. I've had this in a notebook I had been working on for a few days, but I present you with a step-by-step toy example scenario which recreates the problem.

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

In this scenario you want to create a Manipulate showing a plot of a nice wavy function, so you define this (please make a window size like this, this is important):

该定义很好,因此我们下次将其保留,并将其设为初始化单元.接下来,我们添加Manipulate并执行它.

The definition is nice, so we keep it for the next time and make it an initialization cell. Next we add the Manipulate, and execute it too.

f[x_] := x^2

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

所有作品都很棒,操纵起来真的很棒,这是美好的一天.

All works great, the Manipulate really shines, it is a good day.

只要是偏执的自我,您就可以检查定义是否正确:

Just being your paranoid self you check whether the definition is OK:

是的,一切仍在检查中.美好的.但是现在您想到,一个更好的波浪函数将是一个正弦波,因此您可以更改定义,执行并变得偏执,请检查:

Yeah, everything still checks out. Fine. But now it occurs to you that a better wavy function would be a sine, so you change the definition, execute, and being paranoid, check:

一切仍然很好.经过一天的辛苦工作,您已经做好准备,可以保存工作并退出. [退出内核]

Everything still fine. You're ready from a day's hard work you save your work and quit. [Quit kernel]

第二天.您再次开始工作.您可以评估笔记本中的初始化单元.定义还好吗?检查.

Next day. You start your work again. You evaluate the initialization cells in your notebook. Definition still good? Check.

现在,您向下滚动到Manipulate框(由于SaveDefinitions,无需重新执行),并使用滑块进行一些操作.然后向上滚动.

Now, you scroll down to your Manipulate box (no need to re-execute thanks to the SaveDefinitions), play a little with the slider. And scroll back up.

对自己很偏执,您再次检查f的定义:

Being the paranoid you, you once more check the definition of f:

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

Lo and behold, someone has changed the definition behind your back! And nothing executed between your first and second Information(?) check according to the In[] numbers (In[1]: def of f, In[2] first ?, In[3] second ?).

发生了什么事?好吧,这当然是Manipulate. FullForm揭示了其内部结构:

What happened? Well, it's the Manipulate of course. A FullForm reveals its internal structure:

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

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

There you have the culprit. The initialization part of the box defines f again, but it's the old version because we didn't re-evaluate the Manipulate after modifying its definition. As soon as the manipulate box gets on the screen, it is evaluated and you've got your old definition back. Globally!

当然,在这个玩具示例中,很明显,正在发生奇怪的事情.就我而言,我在一个较大的笔记本电脑中有一个较大的模块,在经过一些调试后,其中的一小部分发生了变化.它似乎有效,但是第二天,又一次被我困扰的错误再次被击中.我花了几个小时才意识到,我曾经从各个方面研究过这个问题的几个操作中的一个正在执行此操作.

Of course, in this toy example it is immediately clear something strange is happening. In my case, I had a larger module in a larger notebook in which I, after some debugging, had changed a small part. It seemed to work, but the next day, the same bug that had bugged me before hit again. It took me a couple of hours before I realized that one of the several Manipulates that I used to study the problem at hand from all sides was doing this.

很显然,我很想说这是不想要的行为.现在,对于强制性问题:除了每次您更改可能会重新定义笔记本的每个Manipulate之外,我们还可以采取什么措施来防止Manipulate的这种落后行为?被他们使用了吗?

Clearly, I'm tempted to say, this is unwanted behavior. Now, for the obligatory question: what can we do to prevent this behind-your-back behavior of Manipulate from occurring other than re-executing every Manipulate in your notebook each time you change a definition that might be used by them?

推荐答案

这里是尝试.这个想法是在操纵代码中用DownValues或其他...Values标识符号,并使用唯一的变量/符号代替它们自动重命名它们.在克隆符号功能的帮助下,可以非常优雅地执行此处的想法,我有时会发现这很有用.下面的函数clone将克隆给定的符号,从而产生具有相同全局定义的符号:

Here is an attempt. The idea is to identify symbols with DownValues or some other ...Values inside your manipulated code, and automatically rename them using unique variables / symbols in place of them. The idea here can be executed rather elegantly with the help of cloning symbols functionality, which I find useful from time to time. The function clone below will clone a given symbol, producing a symbol with the same global definitions:

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.我将使用另一个:我将介绍的一些自定义包装器的UpValues轻轻地重载Manipulate.我将其称为CloneSymbols.这是代码:

There are several alternatives of how to implement the function itself. One is to introduce the function with another name, taking the same arguments as Manipulate, say myManipulate. I will use another one: softly overload Manipulate via UpValues of some custom wrapper, that I will introduce. I will call it CloneSymbols. Here is the code:

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包装器中:

Note that to use the new functionality, one has to wrap the SaveDefinitions->True option in CloneSymbols wrapper:

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

这不会影响Manipulate内代码中原始符号的定义,因为它们是其副本的副本,其定义已被保存并用于初始化.我们可以查看FullForm来确认Manipulate

This will not affect the definitions of original symbols in the code inside Manipulate, since it were their clones whose definitions have been saved and used in initialization now. We can look at the FullForm for this Manipulate to confirm that:

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]]]]]

尤其是,您可以将函数的定义更改为

In particular, you can change the definitions of functions to say

f[x_]:=Cos[x];
g[x_]:=x;

然后移动上面产生的Manipulate的滑块,然后检查功能定义

Then move the slider of the Manipulate produced above, and then check the function definitions

?f
Global`f
f[x_]:=Cos[x]

?g
Global`g
g[x_]:=x

Manipulate合理地独立于任何东西,可以安全地复制和粘贴.这里发生的事情如下:我们首先找到所有具有不平凡的DownValuesSubValuesUpValues的符号(也可以添加OwnValues),然后使用Casesclone创建他们的克隆飞翔.然后,将所有克隆的符号用词法替换为Manipulate中的克隆,然后让Manipulate保存克隆的定义.这样,我们就可以对所涉及的功能进行快照",但不会以任何方式影响原始功能.

This Manipulate is reasonably independent of anything and can be copied and pasted safely. What happens here is the following: we first find all symbols with non-trivial DownValues, SubValues or UpValues (one can probably add OwnValues as well), and use Cases and clone to create their clones on the fly. We then replace lexically all the cloned symbols with their clones inside Manipulate, and then let Manipulate save the definitions for the clones. In this way, we make a "snapshot" of the functions involved, but do not affect the original functions in any way.

已使用unique函数解决了克隆(符号)的唯一性.但是请注意,尽管以这种方式获得的Manipulate-不会威胁到原始函数定义,但它们通常仍将依赖于它们,因此人们不能认为它们完全独立于任何事物.人们将不得不走下依赖树并在那里克隆所有符号,然后重建它们之间的依赖关系,以在Manipulate中构造一个完全独立的快照".这是可行的,但更复杂.

The uniqueness of the clones (symbols) has been addressed with the unique function. Note however, that while the Manipulate-s obtained in this way do not threaten the original function definitions, they will generally still depend on them, so one can not consider them totally independent of anything. One would have to walk down the dependency tree and clone all symbols there, and then reconstruct their inter-dependencies, to construct a fully standalone "snapshot" in Manipulate. This is doable but more complicated.

编辑

对于@Sjoerd的每个请求,我添加了一种代码,用于当我们确实希望Manipulate -s更新到函数的更改,但又不希望它们主动干预和更改任何全局定义的情况.我建议使用一种指针"技术的变体:我们将再次用新的符号替换函数名称,但是,我们将使用ManipulateInitialization选项简单地使这些符号替换,而不是在函数之后克隆这些新的符号.符号是我们函数的指针",例如Initialization:>{new1:=f,new2:=g}.显然,重新评估此类初始化代码不会损害fg的定义,与此同时,我们的Manipulate -s将对这些定义的更改做出响应.

Per request of @Sjoerd, I add code for a case when we do want our Manipulate-s to update to the function's changes, but do not want them to actively interfere and change any global definitions. I suggest a variant of a "pointer" technique: we will again replace function names with new symbols, but, rather than cloning those new symbols after our functions, we will use the Manipulate's Initialization option to simply make those symbols "pointers" to our functions, for example like Initialization:>{new1:=f,new2:=g}. Clearly, re-evaluation of such initialization code can not harm the definitions of f or g, and at the same time our Manipulate-s will become responsive to changes in those definitions.

首先想到的是,我们可以简单地用新符号替换函数名称,然后让Manipulate初始化自动完成其余的工作.不幸的是,在此过程中,它遍历了依赖关系树,因此,我们的函数的定义也将包括在内,这是我们要避免的方法.因此,我们将显式构造Initialize选项.这是代码:

The first thought is that we could just simply replace function names by new symbols and let Manipulate initialization automatically do the rest. Unfortunately, in that process, it walks the dependency tree, and therefore, the definitions for our functions would also be included - which is what we try to avoid. So, instead, we will explicitly construct the Initialize option. Here is the code:

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]]]

具有与以前相同的定义:

With the same definitions as before:

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

这是产生的ManipulateFullForm:

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-将响应我们功能的更新,同时对主要功能的定义无害.要付出的代价是它们不是独立的,并且如果未定义主要功能,它们将无法正确显示.因此,根据需要,可以使用CloneSymbols包装器或SavePointers.

The newly generated symbols serve as "pointers" to our functions. The Manipulate-s constructed with this approach, will be responsive for updates in our functions, and at the same time harmless for the main functions' definitions. The price to pay is that they are not self-contained and will not display correctly if the main functions are undefined. So, one can use either CloneSymbols wrapper or SavePointers, depending on what is needed.

这篇关于保存定义被认为是危险的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆