tom*_*omd 28 wolfram-mathematica
我想描述我一直有一个问题与Plot使用With,以保持定义的参数"本地".我不一定要求解决问题:我遇到的问题是理解问题.
有时候我使用如下的结构来获得一个Plot:
方法1
plot1 = With[{vmax = 10, km = 10},
Plot[Evaluate@((vmax x)/(km + x)), {x, 0, 100},
AxesOrigin -> {0, 0}]]
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法,即使对于非Mathematica用户来说,它也是相当清楚的.
当要绘制的方程式变得更加复杂时,我喜欢在绘图外部定义它们(使用SetDelayed).例如:
f[x_] := (vmax x)/(km + x)
Run Code Online (Sandbox Code Playgroud)
但是,以下不起作用
方法2
plot2 = With[{vmax = 10, km = 10},
Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]
Run Code Online (Sandbox Code Playgroud)
我一直天真地认为它应该.但是,基于Help语句
Plot将变量x视为本地,有效地使用Block
我使用过各种解决方法,大多数情况如下
方法3
plot3 = Plot[With[{vmax = 10, km = 10}, Evaluate@f[x]], {x, 0, 100},
AxesOrigin -> {0, 0}]
Run Code Online (Sandbox Code Playgroud)
这个看起来很尴尬,甚至通常需要对Mathematica用户进一步解释.
绘图输出

但是,最近我发现偶然的机会,取代Block了With在方法2工程完全按照预期.
例如,我可以做类似以下的事情(这对我来说似乎是一种非常通用的方法):
plot4 = Block[{vmax = {10, 10, 10}, km = { 10, 100, 1000}},
Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0},
PlotStyle -> {Red, Green, Blue}]]
Run Code Online (Sandbox Code Playgroud)
给

我的问题如下.With方法1和2 中行为差异的解释是什么?我应该预期方法2不起作用吗?此外,对方法2中的Block和行为差异的解释是什么With?我是否应该能够预测它Block会起作用?
有趣的是,那些比我更有经验的人向我建议了许多变通办法,但没有人建议使用Block.
最后,我需要保持vmax和km本地化.(它们已在其他地方以代数方式定义)
Leo*_*rin 62
你的问题不Plot在于关于范围构造如何工作的问题.这里的主要困惑是由于词汇和动态范围之间的差异.而罪魁祸首就是这个定义:
f[x_] := (vmax x)/(km + x)
Run Code Online (Sandbox Code Playgroud)
它的问题在于它f隐含地依赖于全局符号(变量)vmax和km.我非常反对这种结构,因为它们导致了无限的混乱.现在,可以通过以下示例说明会发生什么:
In[55]:= With[{vmax =1, km = 2},f[x]]
Out[55]= (vmax x)/(km+x)
Run Code Online (Sandbox Code Playgroud)
要理解为什么会发生这种情况,就必须了解词法范围的含义.我们知道它With有一个HoldAll属性.它的工作方式是,它看起来是什么字面上里面,并代发现变量字面上的身体从声明列表中的值.这发生在变量绑定阶段,只有这样才能让身体进行评估.由此可见,以下内容将起作用:
In[56]:= With[{vmax =1, km = 2},Evaluate[f[x]]]
Out[56]= x/(2+x)
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为它Evaluate会覆盖HoldAll属性的"部分" With,迫使身体在其他任何事情之前进行评估(变量绑定和后续的身体评估).因此,With[{vmax = 1, km = 2}, (vmax x)/(km + x)]正如您所看到的那样,它完全等同于上面的使用Trace.拼图的下一部分是为什么
With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]
Run Code Online (Sandbox Code Playgroud)
不起作用.这是因为这次我们不首先评估身体.的存在Evaluate只影响f[x]内部Plot,而不是评估Plot其自身内部With.这说明了
In[59]:= With[{vmax = 10, km = 10}, q[Evaluate@f[x]]]
Out[59]= q[(vmax x)/(km + x)]
Run Code Online (Sandbox Code Playgroud)
此外,我们不想Plot先评估,因为那时价值vmax和km将不会被定义.然而,所有With看到的都是f[x],并且因为参数vmax并km没有字面上存在(词法范围,记住),所以不会进行替换.我们应该Block在这里使用,事情会起作用,因为Block它使用动态范围,意味着它会及时重新定义值(如果你愿意,可以重新定义执行堆栈的一部分),而不是就地.因此,使用Block[{a =1, b =2}, ff[x]]where ff隐含地取决于a并且b(大致)等价于a=1;b=2;ff[x](在保留范围之后与差异a并b恢复其全局值Block).所以,
In[60]:= Block[{vmax = 10, km = 10}, q[Evaluate@f[x]]]
Out[60]= q[(10 x)/(10 + x)]
Run Code Online (Sandbox Code Playgroud)
要使With版本有效,您必须为f[x](rhs)注入表达式,例如:
In[63]:= Unevaluated[With[{vmax = 10, km = 10}, q[f[x]]]] /. DownValues[f]
Out[63]= q[(10 x)/(10 + x)]
Run Code Online (Sandbox Code Playgroud)
请注意,这不起作用:
In[62]:= With[{fx = f[x]}, With[{vmax = 10, km = 10}, q[fx]]]
Out[62]= q[(vmax x)/(km + x)]
Run Code Online (Sandbox Code Playgroud)
但这里的原因是非常微妙的:当外部With评估在内部评估之前,它会发现变量名称冲突并重命名其变量.规则更具破坏性,它们不尊重内部范围构造.
编辑
如果一个人坚持嵌套With-s,那么人们如何可以欺骗名称冲突解决机制With并使其工作:
In[69]:= With[{fx = f[x]}, With @@ Hold[{vmax = 10, km = 10}, q[fx]]]
Out[69]= q[(10 x)/(10 + x)]
Run Code Online (Sandbox Code Playgroud)
由于外部With不再能检测到内部的存在With(使用Apply[With,Hold[...]]使内部With有效地动态生成),因此它不会进行任何重命名,然后它就可以工作.当你不想重命名时,这是愚弄词法作用域名称解析机制的一般技巧,尽管使用它的必要性通常表明设计不好.
结束编辑
但我离题了.总而言之,让你的第二个方法工作非常困难,并且需要非常奇怪的构造
Unevaluated[ With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100},
AxesOrigin -> {0, 0}]]] /. DownValues[f]
Run Code Online (Sandbox Code Playgroud)
要么
With[{fx = f[x]},
With @@ Hold[{vmax = 10, km = 10},
Plot[Evaluate@fx, {x, 0, 100}, AxesOrigin -> {0, 0}]]]
Run Code Online (Sandbox Code Playgroud)
再一次:所有这一切都是因为With必须在代码中明确地"看到"变量才能进行替换.相比之下,Block不需要它,它根据修改后的全局值在评估时动态替换值,就好像你进行了分配一样,这就是它工作的原因.
现在,真正的罪魁祸首是你的定义f.如果您f使用显式参数传递定义了您,可以避免所有这些麻烦:
ff[x_, vmax_, km_] := (vmax x)/(km + x)
Run Code Online (Sandbox Code Playgroud)
现在,这开箱即用:
With[{vmax = 10, km = 10},
Plot[Evaluate@ff[x, vmax, km], {x, 0, 100}, AxesOrigin -> {0, 0}]]
Run Code Online (Sandbox Code Playgroud)
因为参数显式出现在函数调用签名中,因此可见With.
总结一下:你观察到的是词汇和动态范围之间相互作用的结果.词法范围构造必须在变量绑定阶段(评估之前)在代码中明确地"看到"它们的变量,否则它们将无效.动态范围有效地修改符号的值,并且在这个意义上要求较低(您支付的代价是使用大量动态范围的代码更难以理解,因为它混合了状态和行为).出现问题的主要原因是函数定义会对全局符号(不在函数的形式参数列表中)进行隐式依赖.最好避免这种结构.仍然有可能使事情有效,但这要复杂得多(如上所述),至少在手头的情况下,没有充分的理由.