Mathematica的可靠清理

时间:2010-07-29 18:27:02

标签: error-handling wolfram-mathematica

无论好坏,Mathematica提供了丰富的结构,允许您进行非本地控制转移,包括ReturnCatch / ThrowAbortGoto。但是,这些非本地控制转移通常与编写需要确保清理代码(如关闭流)运行的强大程序相冲突。许多语言提供了确保清理代码在各种情况下运行的方法; 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[##]) &]];

这肯定不会赢得任何选美比赛,但它也只会处理AbortThrow。特别是,它在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并且实际上似乎没有工作(虽然尝试使用它是完全楔入内核的好方法!)

3 个答案:

答案 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

在评估CleanUpbody的正文返回(因为CleanUpHoldAll):

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声明,最好使用ThrowCatch来突破评估并“返回”某些内容以达到您想要的程度要归还。

那就是说,我意识到你可能并不总是能够控制你必须清理的代码,所以你总是可以将表达式包装在CleanUp中的无操作控制结构中,例如:

ret1 = Do[ret2 = expr, {1}]

...滥用Do强制Return中未包含的expr未包含在Do的控制结构中,以退出ret1循环。唯一棘手的部分(我认为,没有尝试过这个)是必须处理上面的两个不同的返回值:Return将包含非包含ret2的值,但expr会有{{1}}的任何其他评估的价​​值。可能有一种更清洁的方法来解决这个问题,但我现在看不到它。

HTH!

答案 1 :(得分:3)

CleanUp

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[##]) &]];