Надежная уборка в Mathematica - PullRequest
13 голосов
/ 29 июля 2010

Что бы там ни было, 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;Я полагаю, что если вы используете Goto для такого рода нелокального управления в Mathematica, вы заслуживаете того, что получаете.

Я не вижу хорошего способа обойти это.Например, 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"]]

вернет "связку" без очистки печати.Я не вижу способа обойти это без утомительного, подверженного ошибкам и, возможно, невозможного обхода кода или как-то локально переопределения Return с использованием Block, что является отвратительно хакерским и, похоже, на самом деле не работает (хотя экспериментирует с этимотличный способ полностью заклинить ядро!)

Ответы [ 3 ]

3 голосов
/ 22 августа 2010

Более поздняя версия 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 голосов
/ 30 июля 2010

Отличный вопрос, но я не согласен с тем, что семантика 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. Вероятно, есть более понятный способ справиться с этим, но сейчас я не вижу этого.

НТН!

2 голосов
/ 06 августа 2010

Майкл Пилат предоставил трюк с ключом для "перехвата" возвратов, но я закончил тем, что использовал его немного по-другому, используя тот факт, что Return также вызывает возвращаемое значение именованной функциив качестве управляющих структур, таких как Do.Я сделал выражение, которое очищается после, в значение down локального символа, например так:

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[##]) &]];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...