无论好坏,Mathematica提供了丰富的结构,允许您进行非本地控制转移,包括Return
,Catch
/ Throw
,Abort
和Goto
。但是,这些非本地控制转移通常与编写需要确保清理代码(如关闭流)运行的强大程序相冲突。许多语言提供了确保清理代码在各种情况下运行的方法; Java有finally
个块,C ++有析构函数,Common Lisp有UNWIND-PROTECT
,依此类推。
在Mathematica,我不知道如何完成同样的事情。我有一个看起来像这样的部分解决方案:
Attributes[CleanUp] = {HoldAll};
CleanUp[body_, form_] :=
Module[{return, aborted = False},
Catch[
CheckAbort[
return = body,
aborted = True];
form;
If[aborted,
Abort[],
return],
_, (form; Throw[##]) &]];
这肯定不会赢得任何选美比赛,但它也只会处理Abort
和Throw
。特别是,它在Return
存在时失败;我想如果你在Mathematica中使用Goto
来做这种非局部控制,你应得的。
我没有看到这方面的好方法。例如,没有CheckReturn
,当你接下来时,Return
的语义非常模糊。有没有我缺少的技巧?
编辑: Return
的问题及其定义的模糊性与其与条件的交互有关(在某种程度上它不是Mathematica中的“控制结构”)。例如,使用我的CleanUp
表单:
CleanUp[
If[2 == 2,
If[3 == 3,
Return["foo"]]];
Print["bar"],
Print["cleanup"]]
这将返回“foo”而不打印“cleanup”。同样地,
CleanUp[
baz /.
{bar :> Return["wongle"],
baz :> Return["bongle"]},
Print["cleanup"]]
将返回“bongle”而不打印清理。我没有看到这种方法,没有繁琐,容易出错,可能不可能的代码行走或以某种方式在本地重新定义Return
使用Block
,这是非常hacky并且实际上似乎没有工作(虽然尝试使用它是完全楔入内核的好方法!)
答案 0 :(得分:3)
很好的问题,但我不同意Return
的语义是模糊的;它们记录在您提供的链接中。简而言之,Return
退出调用它的最内层构造(即控制结构或函数定义)。
上述CleanUp
函数无法从Return
清除的唯一情况是直接传递单个或CompoundExpression
(例如(one;two;three)
直接作为输入它。
返回退出函数f
:
In[28]:= f[] := Return["ret"]
In[29]:= CleanUp[f[], Print["cleaned"]]
During evaluation of In[29]:= cleaned
Out[29]= "ret"
Return
退出x
:
In[31]:= x = Return["foo"]
In[32]:= CleanUp[x, Print["cleaned"]]
During evaluation of In[32]:= cleaned
Out[32]= "foo"
Return
退出Do
循环:
In[33]:= g[] := (x = 0; Do[x++; Return["blah"], {10}]; x)
In[34]:= CleanUp[g[], Print["cleaned"]]
During evaluation of In[34]:= cleaned
Out[34]= 1
在评估CleanUp
时body
的正文返回(因为CleanUp
为HoldAll
):
In[35]:= CleanUp[Return["ret"], Print["cleaned"]];
Out[35]= "ret"
In[36]:= CleanUp[(Print["before"]; Return["ret"]; Print["after"]),
Print["cleaned"]]
During evaluation of In[36]:= before
Out[36]= "ret"
正如我上面提到的,后两个例子是我可以设想的唯一有问题的案例(虽然我可能是错的)但可以通过向CleanUp
添加定义来处理它们:
In[44]:= CleanUp[CompoundExpression[before___, Return[ret_], ___], form_] :=
(before; form; ret)
In[45]:= CleanUp[Return["ret"], Print["cleaned"]]
During evaluation of In[46]:= cleaned
Out[45]= "ret"
In[46]:= CleanUp[(Print["before"]; Return["ret"]; Print["after"]),
Print["cleaned"]]
During evaluation of In[46]:= before
During evaluation of In[46]:= cleaned
Out[46]= "ret"
正如你所说,不会赢得任何选美比赛,但希望这有助于解决你的问题!
回复您的更新
我认为在Return
内使用If
是不必要的,甚至滥用Return
,因为If
已经返回基于的第二个或第三个参数第一个参数中的条件状态。虽然我意识到您的示例可能是人为的,但If[3==3, Return["Foo"]]
在功能上与If[3==3, "foo"]
如果您有更复杂的If
声明,最好使用Throw
和Catch
来突破评估并“返回”某些内容以达到您想要的程度要归还。
那就是说,我意识到你可能并不总是能够控制你必须清理的代码,所以你总是可以将表达式包装在CleanUp
中的无操作控制结构中,例如:
ret1 = Do[ret2 = expr, {1}]
...滥用Do
强制Return
中未包含的expr
未包含在Do
的控制结构中,以退出ret1
循环。唯一棘手的部分(我认为,没有尝试过这个)是必须处理上面的两个不同的返回值:Return
将包含非包含ret2
的值,但expr
会有{{1}}的任何其他评估的价值。可能有一种更清洁的方法来解决这个问题,但我现在看不到它。
HTH!
答案 1 :(得分:3)
Pillsy's later version是一个不错的选择。有迂腐的风险,我必须指出一个棘手的用例:
Catch[CleanUp[Throw[23], Print["cleanup"]]]
问题是由于人们无法明确指定 Catch 的标记模式,该标记模式将与未标记的 Throw 匹配。
以下版本的 CleanUp 解决了这个问题:
SetAttributes[CleanUp, HoldAll]
CleanUp[expr_, cleanup_] :=
Module[{exprFn, result, abort = False, rethrow = True, seq},
exprFn[] := expr;
result = CheckAbort[
Catch[
Catch[result = exprFn[]; rethrow = False; result],
_,
seq[##]&
],
abort = True
];
cleanup;
If[abort, Abort[]];
If[rethrow, Throw[result /. seq -> Sequence]];
result
]
唉,这段代码在选美比赛中更不具备竞争力。此外,如果有人跳进了另一个非本地控制流程,而这个代码将无法处理,那么我也不会感到惊讶。即使在现在处理所有可能情况的不太可能的情况下,也可以在Mathematica X (其中X> 7.01)中引入有问题的情况。
我担心在Wolfram为此目的明确引入新的控制结构之前,不能对这个问题做出明确的答案。 UnwindProtect 将是这样一个设施的好名字。
答案 2 :(得分:2)
迈克尔·皮拉特提供了the key trick的“捕获”回报,但我最终以稍微不同的方式使用它,使用Return
强制命名函数的返回值以及控制的事实像Do
这样的结构。我将正在被清理的表达式转换为本地符号的向下值,如下所示:
Attributes[CleanUp] = {HoldAll};
CleanUp[expr_, form_] :=
Module[{body, value, aborted = False},
body[] := expr;
Catch[
CheckAbort[
value = body[],
aborted = True];
form;
If[aborted,
Abort[],
value],
_, (form; Throw[##]) &]];