Что бы там ни было 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 и, на самом деле кажется, не работает (хотя экспериментирование с ним является отличным способом полностью втиснуть ядро!)
Отличный вопрос, но я не согласен с тем, что семантика 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!
Майкл Пилат предоставил ключевой трюк для «улавливания» результатов, но в итоге я использовал его несколько иначе, используя тот факт, что 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[##]) &]];
Более поздняя версия Пилси из 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 было бы прекрасным названием для такого объекта.