2010-07-29 15 views
13

是好還是壞,Mathematica提供了豐富的結構,讓你做控制的非本地傳輸,包括ReturnCatch/ThrowAbortGoto的。但是,這種非本地控制轉移通常會與編寫健壯的程序相沖突,這些程序需要確保清理代碼(如關閉流)運行。許多語言提供了確保清理代碼在各種情況下運行的方法; Java有它的finally塊,C++有析構函數,Common Lisp有UNWIND-PROTECT等等。可靠的清理Mathematica中

在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的存在下失敗;我想如果你使用Goto在Mathematica中做這種非本地控制,你應該得到你所得到的。

我沒有看到一個很好的解決方法。例如,沒有CheckReturn,當你正確地做到這一點時,Return具有非常模糊的語義。有沒有我錯過的技巧?

編輯:Return的問題,並在其定義中的模糊性,具有與其用條件(這在某種程度上不在數學「控制結構」)的相互作用有關。一個例子,用我的CleanUp形式:

CleanUp[ 
If[2 == 2, 
    If[3 == 3, 
    Return["foo"]]]; 
Print["bar"], 

Print["cleanup"]] 

這將返回「富」不打印「清理」。同樣,

CleanUp[ 
baz /. 
    {bar :> Return["wongle"], 
    baz :> Return["bongle"]}, 

Print["cleanup"]] 

將返回「bongle」而不打印清理。我沒有找到解決這個問題的方法,沒有繁瑣的,容易出錯的,也許不可能的代碼散步,或者使用Block以某種方式在本地重新定義Return,這很荒謬,實際上似乎並不奏效(儘管試驗它是一種好方法以完全楔入內核!)

+0

CheckAll似乎爲這個問題提供了一個相當不錯的解決方案。在Mathematica StackExchange上查看[這個答案](http://mathematica.stackexchange.com/a/48493/142)。 – WReach 2014-05-29 03:37:30

回答

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 

返回10在其中body被評爲點(因爲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" 

正如你所說,不會贏得任何選美比賽,但希望這可以幫助您解決問題!

響應您的更新

我認爲使用ReturnIf是不必要的,甚至Return的濫用,因爲If基於條件的狀態已經返回第二個或第三個參數在第一個參數。雖然我知道你的例子可能是人爲的,If[3==3, Return["Foo"]]在功能上等同於If[3==3, "foo"]

如果你有更復雜的If聲明,你最好使用ThrowCatch打出來的評價和「返回」的東西的點你想它被返回。

這麼說,我知道你可能並不總是有超過你必須清理後的代碼控制,所以你總是可以在無操作的控制結構包裹在CleanUp的表達,如:

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

...通過濫用Do強制不包含在expr的控制結構中以返回Do循環。唯一棘手的部分(我認爲,沒有嘗試過)必須處理上述兩個不同的返回值:ret1將包含未包含的值Return,但ret2將具有任何其他評估值expr的值。可能有更清晰的方法來處理,但我現在看不到它。

HTH!

+0

這是一個值得付出的努力,它適用於最簡單的情況,但不幸的是在條件的情況下失敗。有關更多詳細信息,請參閱編輯的問題 – Pillsy 2010-08-02 13:31:14

+0

我同意在條件內使用'Return'並不是很好的Mathematica風格,但它是大多數命令式語言中非常常見的習慣用法,人們通常希望儘可能直接地從C,Fortran或僞代碼轉換算法。 – Pillsy 2010-08-06 13:40:08

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

Pillsy's later version清理是一個很好的一個。在被迂腐的風險,我必須一個麻煩的用例指出:

Catch[CleanUp[Throw[23], Print["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將是這樣一個設施的好名字。

+1

「CheckAbort [Catch [Catch [」序列將從現在開始在我的惡夢中出現... – 2010-09-10 04:51:49

+0

+1。大部分都很好,但是這個怎麼樣:'In [21]:= CleanUp [a = 0;投擲[未評估[Abort []]],a = 1] Out [21] = $ Aborted'。我希望輸出爲'Hold [Throw [Abort []]]',並在頂層有一個未捕獲異常的消息。同樣,這個'CleanUp [拋出[未評估[Throw [$ Failed]]],a = 1]'也會破壞你的代碼。我認爲解決這個問題的唯一方法是在代碼執行過程中通過Gayley - Villegas技巧動態重新定義'Throw',並暫時給'Throw'賦予'HoldAllComplete'屬性,但這並不完美。這是一個無標籤'Catch'的缺陷... – 2011-09-24 14:03:29

+0

...因爲對於標籤的例外,可以避免:'Catch [拋出[未評估[中止[]],標籤],_,HoldComplete]'。實際上,我認爲無標記的例外完全是一種語言缺陷 - 對於一個稍微更方便的「Throw」形式,我們可以獲得不止一個門,以便微妙地開放漏洞。 – 2011-09-24 14:12:36