Ваш вопрос не столько о 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)
Чтобы понять, почему это происходит, нужно понять, что означает лексическая область видимости. Мы знаем, что 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
.
Подводя итог: то, что вы наблюдали, является следствием взаимодействия лексической и динамической областей видимости.Лексические ограничивающие конструкции должны «видеть» свои переменные явно в коде на этапе привязки переменных (до оценки), иначе они не будут эффективными.Динамическая область видимости эффективно изменяет значения символов и в этом смысле менее требовательна (цена, которую вы платите, состоит в том, что код, использующий много динамической области видимости, сложнее понять, поскольку он смешивает состояние и поведение).Основной причиной проблем является определение функции, которая создает неявные зависимости от глобальных символов (которых нет в списке формальных параметров функции).Лучше всего избегать таких конструкций.Все еще можно заставить вещи работать, но это значительно сложнее (как было показано выше), и, по крайней мере, для рассматриваемого случая, без веской причины.