我想描述一下我Plot
使用With
来保持已定义参数'local'的问题。我不一定要求修复:我遇到的问题是理解。
有时我会使用如下构造来获取Plot:
方法1
plot1 = With[{vmax = 10, km = 10},
Plot[Evaluate@((vmax x)/(km + x)), {x, 0, 100},
AxesOrigin -> {0, 0}]]
我喜欢这种方法,即使对于非 Mathematica 用户来说也是相当清楚的。
当要绘制的方程式变得更加复杂时,我喜欢在绘图外部定义它们(使用SetDelayed)。例如:
f[x_] := (vmax x)/(km + x)
但是,以下不起作用
方法2
plot2 = With[{vmax = 10, km = 10},
Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]
我总是天真地认为它应该。但是,基于帮助声明
Plot将变量x视为本地, 有效地使用Block
我使用了各种变通办法,大多数情况如下
方法3
plot3 = Plot[With[{vmax = 10, km = 10}, Evaluate@f[x]], {x, 0, 100},
AxesOrigin -> {0, 0}]
这个看起来很尴尬,通常需要进一步解释 Mathematica 用户。
绘制输出
然而,最近我偶然发现在方法2中用Block
代替With
与预期完全一样。
plot4 = Block[{vmax = {10, 10, 10}, km = { 10, 100, 1000}},
Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0},
PlotStyle -> {Red, Green, Blue}]]
给予
我的问题如下。方法1和2中With
行为差异的解释是什么?我应该预期方法2不起作用吗?此外,对方法2中Block
和With
的行为差异有何解释?我是否应该能够预测Block
会起作用?
有趣的是,那些比我更有经验的人向我建议了许多变通办法,但没有人建议使用Block
。
最后,我需要保持vmax
和km
本地。(它们已在别处以代数方式定义)
答案 0 :(得分:62)
你的问题不在于Plot
,而在于范围界定结构是如何运作的。这里的主要困惑是由于词汇和动态范围之间的差异。而罪魁祸首就是这个定义:
f[x_] := (vmax x)/(km + x)
它的问题在于它使f
隐含地依赖于全局符号(变量)vmax
和km
。我非常反对这种结构,因为它们导致了无限的混乱。现在,可以通过以下示例说明会发生什么:
In[55]:= With[{vmax =1, km = 2},f[x]]
Out[55]= (vmax x)/(km+x)
要理解为什么会发生这种情况,我们必须了解 lexical 范围的含义。我们知道With
具有HoldAll
属性。它的工作方式是它看起来就像字面上一样,并用声明列表中的值替换正文中字面的变量。这发生在变量绑定阶段,只有这样才能让身体进行评估。由此可见,以下内容将起作用:
In[56]:= With[{vmax =1, km = 2},Evaluate[f[x]]]
Out[56]= x/(2+x)
这很有效,因为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}]]
不起作用。这是因为这次我们不首先评估身体。 Evaluate
的存在仅影响f[x]
内的Plot
,而不影响Plot
内With
本身的评估。
In[59]:= With[{vmax = 10, km = 10}, q[Evaluate@f[x]]]
Out[59]= q[(vmax x)/(km + x)]
此外,我们不希望首先评估Plot
,因为此时不会定义vmax
和km
的值。但是,With
看到的所有内容都是f[x]
,因为参数vmax
和km
不是字面上存在于那里(词汇作用域,请记住),不会进行替换。我们应该在这里使用Block
,事情会有效,因为Block
使用动态范围,这意味着它会及时重新定义值(如果你愿意,可以重新定义执行堆栈的一部分),而不是就地。因此,使用Block[{a =1, b =2}, ff[x]]
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)]
要使With
版本正常工作,您必须为f[x]
(r.h.s)注入表达式,例如:
In[63]:= Unevaluated[With[{vmax = 10, km = 10}, q[f[x]]]] /. DownValues[f]
Out[63]= q[(10 x)/(10 + x)]
请注意,这不起作用:
In[62]:= With[{fx = f[x]}, With[{vmax = 10, km = 10}, q[fx]]]
Out[62]= q[(vmax x)/(km + x)]
但是这里的原因是非常微妙的:当外部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)]
由于外部With
无法再检测到内部With
的存在(使用Apply[With,Hold[...]]
使内部With
有效地动态生成),因此它不会进行任何重命名,然后它的工作原理。当你不想重命名时,这是愚弄词法作用域名称解析机制的一般技巧,尽管使用它的必要性通常表明设计不好。
结束编辑
但我离题了。总而言之,让你的第二种方法工作非常困难,并且需要非常奇怪的结构,比如
Unevaluated[ With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100},
AxesOrigin -> {0, 0}]]] /. DownValues[f]
或
With[{fx = f[x]},
With @@ Hold[{vmax = 10, km = 10},
Plot[Evaluate@fx, {x, 0, 100}, AxesOrigin -> {0, 0}]]]
再一次:所有这一切都是因为With
必须在代码中明确地“看到”变量才能进行替换。相比之下,Block
不需要它,它会根据修改后的全局值在评估时动态替换值,就好像您进行了分配一样,这就是它工作的原因。
现在,真正的罪魁祸首是你对f
的定义。如果您使用显式参数传递来定义f
,则可以避免所有这些麻烦:
ff[x_, vmax_, km_] := (vmax x)/(km + x)
现在,这开箱即用:
With[{vmax = 10, km = 10},
Plot[Evaluate@ff[x, vmax, km], {x, 0, 100}, AxesOrigin -> {0, 0}]]
因为参数显式出现在函数调用签名中,因此With
可见。
总结一下:你观察到的是词汇和动态范围之间相互作用的结果。词法范围构造必须在变量绑定阶段(评估之前)在代码中明确地“看到”它们的变量,否则它们将无效。动态范围有效地修改了符号的值,从这个意义上说要求不高(你付出的代价是使用大量动态范围的代码更难以理解,因为它混合了状态和行为)。出现问题的主要原因是函数定义会对全局符号(不在函数的形式参数列表中)进行隐式依赖。最好避免这种结构。仍然有可能使事情有效,但这要复杂得多(如上所述),至少在手头的情况下,没有充分的理由。
答案 1 :(得分:1)
只有两条评论:
使用Block,您不需要使用Evaluate。也就是说,Block [{vmax = 10,km = 2},Plot [f [x],{x,0,100}]将起作用。
另一种方法是定义替换规则: rule = {vmax - > 10,km - > 10};图[f [x] /。规则,{x,0,100}] 优点是您可以在其他语句中重复使用该规则。