Подтвердить что ты не робот

Надежная очистка в 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, который ужасно взломан и на самом деле не работает (хотя экспериментирует с ним это отличный способ полностью вклинивать ядро!)

4b9b3361

Ответ 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[##]) &]];