Skip to content

Latest commit

 

History

History
99 lines (66 loc) · 3.35 KB

SetDelayedAndSymbolsAsSlots.md

File metadata and controls

99 lines (66 loc) · 3.35 KB

在数值函数定义中使用要求形式参数的函数

常见报错消息:3 不是一个变量

抽象描述

一些内置函数要求用户提供一个符号作为形式参数以及一个包含形式参数的表达式作为函数。如果试图通过 := 来在一些具体数值上复用这些函数,就会出错。

例子

以下代码试图定义 $f(x)=x^2$ 的导函数 $g(x)=f'(x)$ ,并求这个导函数在特定点的值:

ClearAll["`*"]
g[x_] := D[x^2, x]
g[3]

但是报错了,消息是 General::ivar:

"3 is not a valid variable."

返回值也不正确:我们希望它返回 6,而它实际返回了 D[9, 3]

g[3] 被计算为 D[3^2, 3],其第一子表达式 3^2 是一个常函数而不是一个平方函数的表达式,第二子表达式是数值 3 而不是 D 所要求的符号,自然会出错。

解决方案1

ClearAll["`*"]
g[x_] = D[x^2, x]
g[3]

原理

不同之处仅在于把 SetDelayed:= )换成了 Set= )。它们(几乎)只有一个区别,就是 SetDelayed 具有 HoldAll 属性,而 Set 所具有的是一个“更弱的” HoldFirst 属性。

SetDelayedHoldAll 属性保证了它在把 := 两端的表达式存储为一个定义时,不会先计算两端的表达式。从而 x := Print[1] 这一含有 Print 的代码实际并不会执行 Print 指令。所以它进行的定义是 g[x_] :> D[x^2, x]。此后当你计算 g[3] 时,3 会代入所有的 x ,然后才会计算 D[3^2, 3] 从而出错。

SetHoldFirst 属性使得它只保持第一个子表达式不计算,当 = 右端的 D[x^2, x] 被计算为 2x 后,定义才进行,所以它进行的定义是 g[x_] :> 2x 。此后当你计算 g[3] 时,3 直接代入 2x 得到 6

解决方案2

对于定义导函数的需求,我们有一个特别简单的解决方案来替代 D

ClearAll["`*"]
f[x_] := x^2
g[x_] := f'[x]
g[3]

其中撇号 ' 是函数 Derivative 的运算符。这个方法的存在是因为 Derivative 的设计要求它处理的是一个函数的头部(如 f )而不是函数的表达式 x^2 ,从而不需要用户告知表达式中的哪一个变量是函数的形式参数,可以自动地完成计算。

值得一提的是, Derivative 可以处理纯函数,也能计算数值导数(即插值函数 InterpolatingFunction 的差分)。一个函数不一定要与一个符号绑定,如果愿意抛弃定义 g[x_] :> something 的做法,就可以用纯函数来给出最简单的导函数表示方式:#^2&'

不过仍然存在很多内置函数要求用户告知表达式中的形式参数(符号计算函数如Collect 、数值计算函数如FindRoot ),而没有 DerivativeD 这样的替代。

解决方案3

在定义内部调整计算顺序和命名

ClearAll@"`*"
g[x_] := D[tmp^2, tmp] /. tmp -> x
g[3]

解决方案4

利用其它有 “Hold” 能力的函数来调整计算顺序(类似于宏)

ClearAll@"`*"
(g[x_] := #)&@D[x^2, x]
g[3]
ClearAll@"`*"
With[{tmp = D[x^2, x]}
  g[x_] := tmp
]
g[3]

解决方案5

使用非标准计算避免 HoldAll

ClearAll@"`*"
g[x_] := Evaluate@D[x^2, x]
g[3]