Надежная очистка в Mathematica
К лучшему или худшему, Mathematica предоставляет множество конструкций, которые позволяют выполнять нелокальные передачи управления, в том числе Return
, Catch
/Throw
, Abort
и Goto
. Однако эти виды нелокальных передач контроля часто конфликтуют с написанием надежных программ, которые должны гарантировать, что код очистки (например, закрывающие потоки) будет запущен. Многие языки предоставляют способы обеспечения того, чтобы код очистки запускался в самых разных обстоятельствах; Java имеет свои блоки finally
, у С++ есть деструкторы, 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
; Я полагаю, что если вы используете Goto
для выполнения такого нелокального управления в Mathematica, вы заслуживаете того, что получаете.
Я не вижу в этом хорошего пути. Например, нет CheckReturn
, и когда вы дойдете до него, Return
имеет довольно мутную семантику. Есть трюк, который мне не хватает?
EDIT: Проблема с 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
, который ужасно взломан и на самом деле не работает (хотя экспериментирует с ним это отличный способ полностью вклинивать ядро!)
Ответы
Ответ 1
Отличный вопрос, но я не согласен с тем, что семантика 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
в структуру управления no-op, например:
ret1 = Do[ret2 = expr, {1}]
..., злоупотребляя Do
, чтобы заставить Return
не содержаться в структуре управления в expr
для возврата из цикла Do
. Единственная сложная часть (я думаю, не пробовав это) - иметь дело с двумя разными значениями возврата выше: ret1
будет содержать значение незанятого Return
, но ret2
будет иметь значение любой другой оценки of expr
. Вероятно, есть более чистый способ справиться с этим, но я не вижу его прямо сейчас.
НТН!
Ответ 2
Pillsy более поздняя версия CleanUp - хорошая. Из-за опасности быть педантичным, я должен указать на неприятный случай использования:
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 будет прекрасным именем для такого объекта.
Ответ 3
Майкл Пилат предоставил ключевой трюк для "ловушек", но я в конечном итоге использовал его несколько иначе, используя тот факт, что 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[##]) &]];