虽然我来晚了,但是我想指出一下形如以下定义的内容:
这些定义可能会使人感到困惑。更好的方式是将“抽象”和“具体”视为相对概念,而不是绝对概念。在这种情况下,“抽象”意味着从高层次上看待事物,而“具体”则意味着从低层次上看待事物。
f[...] := Module[... /; ...]
在这种情况下,定义非常有用。这种类型的定义可以在最终放弃并决定该定义不适用之前执行复杂的计算。
我将说明如何在另一个SO问题的情况下使用它来实现各种错误处理策略。问题是要搜索一组固定的对:
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]
f1[1000]
然而,在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]
总之,这些模式足够简单,可以处理定义域之外的函数参数。在定义函数时,值得花费一些时间来决定如何处理域错误。这样做可以减少调试时间。毕竟,在Mathematica中,几乎所有的函数都是局部函数。考虑以下情况:一个函数可能会被调用以处理字符串、图像、歌曲或漫游的纳米机器人群(也许在Mathematica 9中)。
最后要注意的是...我应该指出,在使用多个定义定义和重新定义函数时,由于“剩余”定义,很容易出现意外结果。作为一般原则,我强烈建议在重复定义函数之前先使用
Clear。
Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...
$AssertFunction
设置为Abort[]
,则它基本上具有与您问题中的函数相同的行为。 - Simon