你的问题并不是关于Plot
因为它是关于范围界定结构如何工作的。这里的主要混乱是由于词法作用域和动态作用域之间的差异造成的。罪魁祸首是这个定义:
f[x_] := (vmax x)/(km + x)
它的问题在于它使得f
隐式依赖于全局符号(变量)vmax
and km
。我非常反对这种结构,因为它们会导致无限的混乱。现在,可以用以下示例来说明发生的情况:
In[55]:= With[{vmax =1, km = 2},f[x]]
Out[55]= (vmax x)/(km+x)
要理解为什么会发生这种情况,我们必须了解lexical范围界定手段。我们知道With
has a 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}]]
不起作用。这是因为这次我们do not先评估身体。的存在Evaluate
仅影响f[x]
inside Plot
,但不评价Plot
本身在里面With
。这可以通过以下方式说明
In[59]:= With[{vmax = 10, km = 10}, q[Evaluate@f[x]]]
Out[59]= q[(vmax x)/(km + x)]
而且,我们不希望Plot
首先评估,然后再评估vmax
and km
将不会被定义。然而,这一切With
看到的是f[x]
,并且由于参数vmax
and km
不是字面上地存在在那里(词法范围,记住),不会进行替换。我们应该使用Block
在这里,事情会成功,因为Block
使用动态作用域,这意味着它会及时重新定义值(如果您愿意,可以是执行堆栈的一部分),而不是就地重新定义值。因此,使用Block[{a =1, b =2}, ff[x]]
where ff
隐含地依赖于a
and b
(大致)相当于a=1;b=2;ff[x]
(不同之处在于a
and 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
在内部变量之前进行评估,它会发现变量名称冲突并重命名其变量。规则更具破坏性,它们不尊重内部范围构造。
EDIT
如果坚持嵌套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
(using Apply[With,Hold[...]]
使内在With
有效地动态生成),它不会进行任何重命名,然后就可以工作了。当您不想重命名时,这是欺骗词法作用域名称解析机制的通用技巧,尽管使用它的必要性通常表明设计不好。
END EDIT
但我离题了。总而言之,让第二种方法发挥作用非常困难,并且需要非常奇怪的结构,例如
Unevaluated[ With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100},
AxesOrigin -> {0, 0}]]] /. DownValues[f]
or
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
.
总结一下:您观察到的是词法作用域和动态作用域之间相互作用的结果。词法作用域构造必须在变量绑定阶段(求值之前)在代码中显式地“查看”它们的变量,否则它们将无效。动态范围有效修改values符号的数量,从这个意义上来说要求不高(您付出的代价是使用大量动态作用域的代码更难理解,因为它混合了状态和行为)。麻烦的主要原因是函数定义对全局符号(不在函数的形式参数列表中)产生隐式依赖。最好避免这样的结构。仍然有可能使事情正常进行,但这要复杂得多(如上面所演示的),并且至少对于当前的情况来说,没有充分的理由。