Надежная очистка в Mathematica

Что бы там ни было 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"]]

Это возвратит "нечто", не печатая "очистку". Аналогично,

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

 Print["cleanup"]]

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

13
задан Pillsy 30 July 2010 в 13:47
поделиться

3 ответа

Отличный вопрос, но я не согласен с тем, что семантика Return туманна; Они задокументированы в предоставленной вами ссылке. Короче говоря, Return завершает самую внутреннюю конструкцию (а именно, структуру управления или определение функции), в которой она вызывается.

Единственный случай, когда ваша функция CleanUp выше не может выполнить очистку из Return , - это когда вы напрямую передаете одно или CompoundExpression (например, (one; two; three) непосредственно в качестве входных данных.

Return выходит из функции 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"

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

HTH!

3
ответ дан 2 December 2019 в 01:41
поделиться

Майкл Пилат предоставил ключевой трюк для «улавливания» результатов, но в итоге я использовал его несколько иначе, используя тот факт, что 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[##]) &]];
2
ответ дан 2 December 2019 в 01:41
поделиться

Более поздняя версия Пилси из CleanUp - хорошая. Рискуя проявить педантичность, я должен указать на неприятный вариант использования:

Catch[CleanUp[Throw[23], Print["cleanup"]]]

Проблема связана с тем, что нельзя явно указать шаблон тега для Catch , который будет соответствовать нетегированному Бросьте .

Следующая версия 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
ответ дан 2 December 2019 в 01:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: