我迟到了,我得到了一个公认的答案,但我想指出表单的定义:
f[...] := Module[... /; ...]
在这种情况下非常有用。这种定义可以在最终退出并确定该定义根本不适用之前执行复杂的计算。
我将说明如何在another SO question 的特定案例的上下文中使用它来实现各种错误处理策略。问题是搜索固定的对列表:
data = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 15}, {5, 29}, {6, 50}, {7,
88}, {8, 130}, {9, 157}, {10, 180}, {11, 191}, {12, 196}, {13,
199}, {14, 200}};
查找第二个分量大于或等于指定值的第一对。一旦找到该对,它的第一个组件将被返回。在 Mathematica 中有很多写法,这里有一种:
f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]
f0[100] (* returns 8 *)
现在的问题是,如果使用无法找到的值调用函数会发生什么?
f0[1000]
error: First::first: {} has a length of zero and no first element.
错误消息是隐秘的,充其量是没有提供任何关于问题所在的线索。如果在调用链的深处调用此函数,则可能会发生一连串类似的不透明错误。
有多种策略可以处理此类异常情况。一种是更改返回值,以便区分成功案例和失败案例:
f1[x_] := Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]
f1[100] (* returns {8} *)
f1[1000] (* returns {} *)
但是,Mathematica 有一个强大的传统,即每当使用其域之外的参数对函数求值时,保持原始表达式不变。这是 Module[... /; ...] 模式可以提供帮助:
f2[x_] :=
Module[{m},
m = Cases[data, {t_, p_} /; p >= x :> t, {1}, 1];
First[m] /; m =!= {}
]
f2[100] (* returns 8 *)
f2[1000] (* returns f2[1000] *)
请注意,如果最终结果是空列表并且原始表达式未计算返回,则 f2 完全退出 - 通过添加 / 的简单权宜之计实现;最终表达式的条件。
如果出现“未找到”的情况,人们可能会决定发出有意义的警告:
f2[x_] := Null /; Message[f2::err, x]
f2::err = "Could not find a value for ``.";
通过此更改,将返回相同的值,但在“未找到”的情况下会发出警告消息。新定义中的 Null 返回值可以是任何东西——它没有被使用。
人们可能会进一步确定“未找到”的情况根本不会发生,除非客户端代码有问题。在这种情况下,应该导致计算中止:
f2[x_] := (Message[f2::err, x]; Abort[])
总之,这些模式很容易应用,因此可以处理定义域之外的函数参数。在定义函数时,花一些时间来决定如何处理域错误是值得的。它以减少调试时间为代价。毕竟,在 Mathematica 中,几乎所有函数都是偏函数。考虑一下:一个函数可能会被一个字符串、一个图像、一首歌或一群纳米机器人(可能在 Mathematica 9 中)调用。
最后的注意事项...我应该指出,在使用多个定义定义和重新定义函数时,由于“遗留”定义很容易得到意想不到的结果。作为一般原则,我强烈建议在多重定义函数之前使用 Clear:
Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...