Предотвращение лавины ошибок времени выполнения в Mathematica

Типичная ситуация, с которой я сталкиваюсь, когда тетрадь выходит за рамки пары функций - я оцениваю выражение, но вместо правильного ответа я получаю звуковой сигнал, за которым следуют десятки ненужных предупреждений, за которыми следует "дальнейший вывод... будет подавлен"

Одна вещь, которая мне показалась полезной - использовать Python-подобный "assert" внутри функций для обеспечения внутренней согласованности. Любые другие советы?

Assert[expr_, msg_] := If[Not[expr], Print[msg]; Abort[], None]

править 11/14 Основная причина появления предупреждающей лавины - когда подвыражение оценивается как "плохое" значение. Это приводит к тому, что родительское выражение оценивается как "плохое" значение, и эта "плохость" распространяется вплоть до корня. Встроенные модули, оцениваемые по пути, замечают недостатки и выдают предупреждения. "Плохо" может означать выражение с неправильным заголовком, списком с неправильным количеством элементов, отрицательно определенной матрицей вместо положительно определенной и т. Д. Обычно это то, что не вписывается в семантику родительского выражения.

Один из способов справиться с этим - переопределить все ваши функции, чтобы они возвращали неоцененные значения при "неверном вводе". Это позаботится о большинстве сообщений, создаваемых встроенными модулями. Встроенные модули, которые выполняют структурные операции, такие как "Часть", все равно будут пытаться оценить вашу ценность и могут выдавать предупреждения.

Отладчик, установленный на "Break on Messages", предотвращает лавину ошибок, хотя это кажется излишним, если он постоянно включен

5 ответов

Решение

Как отмечали другие, существует три способа последовательного устранения ошибок:

  1. правильно набирая параметры и устанавливая условия, при которых ваши функции будут работать,
  2. правильно и последовательно обрабатывать возникающие ошибки, и
  3. упростить вашу методологию, чтобы применить эти шаги.

Как указал Самсдрам, правильная печать ваших функций очень поможет. Не забывайте о : форма Pattern поскольку иногда легче выразить некоторые шаблоны в такой форме, например, x:{{_, _} ..}, Очевидно, что когда этого недостаточно PatternTest с (?) а также Condition с (/;) путь. Самдрам достаточно хорошо это понимает, но я хотел бы добавить, что вы можете создать свой собственный шаблонный тест с помощью чистых функций, например: f[x_?(Head[#]===List&)] эквивалентно f[x_List], Обратите внимание, скобки необходимы при использовании амперсанда в виде чистых функций.

Самый простой способ справиться с ошибками, очевидно, Off или локально Quiet, По большей части, мы все можем согласиться с тем, что плохая идея - полностью отключать сообщения, которые нам не нужны, но Quiet может быть чрезвычайно полезным, когда вы знаете, что делаете что-то, что вызывает жалобы, но в остальном правильно.

Throw а также Catch имеют свое место, но я чувствую, что они должны использоваться только внутри, и ваш код должен сообщать об ошибках через Message объекты. Сообщения могут создаваться так же, как и при настройке сообщения об использовании. Я считаю, что ключ к стратегии последовательной ошибки может быть построен с использованием функций Check, CheckAbort, AbortProtect,

пример

Пример из моего кода OpenAndRead которая защищает от выхода из открытых потоков при прерывании операции чтения, следующим образом:

OpenAndRead[file_String, fcn_]:=
Module[{strm, res},
  strm = OpenRead[file];
  res = CheckAbort[ fcn[strm], $Aborted ];
  Close[strm];
  If[res === $Aborted, Abort[], res] (* Edited to allow Abort to propagate *)
]

который до недавнего времени имел

fcn[ file_String, <otherparams> ] := OpenAndRead[file, fcn[#, <otherparams>]&]
fcn[ file_InputStream, <otherparams> ] := <fcn body>

Тем не менее, это раздражает делать каждый раз.

Вот где решение Belisarius вступает в игру, создав метод, который вы можете использовать последовательно. К сожалению, его решение имеет фатальный недостаток: вы теряете поддержку средств подсветки синтаксиса. Итак, вот альтернатива, которую я придумала для подключения к OpenAndRead сверху

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           fcn[file_Symbol, symbols] := a), {RuleDelayed::"rhs"}]

который имеет использование

MakeCheckedReader[ myReader, a_, b_ ] := {file$, a, b} (*as an example*)

Теперь, проверяя определение myReader дает два определения, как мы хотим. В теле функции, однако, file должен называться file$, (Я еще не понял, как назвать файл var, как мне хотелось бы.)

Редактировать: MakeCheckedReader работает на самом деле ничего не делая сам. Вместо этого TagSet (/:) спецификация сообщает Mathematica, что когда MakeCheckedReader находится на LHS SetDelayed затем замените его нужными определениями функций. Также обратите внимание на использование Quiet; в противном случае он будет жаловаться на закономерности a_ а также b_ появляется в правой части уравнения.

Редактировать 2: Leonid Shifrin указал, как можно использовать file не file$ при определении проверенного читателя. Обновленное решение выглядит следующим образом:

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           SetDelayed @@ Hold[fcn[file_Symbol, symbols], a]), 
           {RuleDelayed::"rhs"}]

Обоснование изменений объясняется в этом его ответе. определяющий myReader, как указано выше, и, проверив его определение, получим

myReader[file$_String,a_,b_]:=OpenAndRead[file$,myReader[#1,a_,b_]&]
myReader[file_Symbol,a_,b_]:={file,a,b}

Я опаздываю на вечеринку, с принятым ответом и все, но я хочу указать, что определения формы:

f[...] := Module[... /; ...]

очень полезны в этом контексте. Определения такого рода могут выполнять сложные вычисления, прежде чем, наконец, выручить и решить, что определение не применимо в конце концов.

Я проиллюстрирую, как это можно использовать для реализации различных стратегий обработки ошибок в контексте конкретного случая из другого вопроса SO. Проблема заключается в поиске фиксированного списка пар:

data = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 15}, {5, 29}, {6, 50}, {7,
     88}, {8, 130}, {9, 157}, {10, 180}, {11, 191}, {12, 196}, {13, 
    199}, {14, 200}};

найти первую пару, чей второй компонент больше или равен указанному значению. Как только эта пара найдена, ее первый компонент должен быть возвращен. Есть много способов написать это в Mathematica, но вот один из них:

f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f0[100] (* returns 8 *)

Теперь возникает вопрос: что произойдет, если функция вызывается со значением, которое не может быть найдено?

f0[1000]
error: First::first: {} has a length of zero and no first element.

Сообщение об ошибке является в лучшем случае загадочным и не дает никаких подсказок о том, в чем заключается проблема. Если эта функция была вызвана глубоко в цепочке вызовов, то, вероятно, произойдет каскад таких же непрозрачных ошибок.

Существуют различные стратегии для решения таких исключительных случаев. Одним из них является изменение возвращаемого значения, чтобы случай успеха можно было отличить от случая отказа:

f1[x_] := Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f1[100] (* returns {8} *)
f1[1000] (* returns {} *)

Тем не менее, существует строгая традиция Mathematica - оставлять исходное выражение неизменным всякий раз, когда функция оценивается с помощью аргументов за пределами своей области. Здесь модуль [... /; ...] шаблон может помочь:

f2[x_] :=
  Module[{m},
    m = Cases[data, {t_, p_} /; p >= x :> t, {1}, 1];
    First[m] /; m =!= {}
  ]

f2[100] (* returns 8 *)
f2[1000] (* returns f2[1000] *)

Обратите внимание, что f2 полностью выдается, если конечный результат представляет собой пустой список, а исходное выражение возвращается без оценки - что достигается простым способом добавления /; условие до окончательного выражения.

Можно принять решение о значительном предупреждении, если произойдет "не найденный" случай:

f2[x_] := Null /; Message[f2::err, x] 
f2::err = "Could not find a value for ``.";

С этим изменением будут возвращены те же значения, но в случае "не найдено" будет выдано предупреждающее сообщение. Возвращаемое значение Null в новом определении может быть любым - оно не используется.

Далее можно решить, что случай "не найден" просто не может возникнуть вообще, за исключением случая с ошибочным клиентским кодом. В этом случае следует прервать вычисление:

f2[x_] := (Message[f2::err, x]; Abort[])

В заключение, эти шаблоны достаточно просты в применении, так что можно иметь дело с аргументами функций, которые находятся за пределами определенной области. При определении функций стоит потратить несколько минут, чтобы решить, как обрабатывать ошибки домена. Это платит за сокращенное время отладки. В конце концов, практически все функции являются частичными функциями в Mathematica. Подумайте: функция может быть вызвана с помощью строки, изображения, песни или роя наноботов (возможно, в Mathematica 9).

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

Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...

Проблема здесь по сути одна из типов. Одна функция выдает неправильный вывод (неправильный тип), который затем подается во многие последующие функции, что приводит к множеству ошибок. Хотя Mathematica не имеет пользовательских типов, как в других языках, вы можете выполнять сопоставление с образцом в аргументах функции без особой работы. Если совпадение не удается, функция не оценивает и, следовательно, не издает ошибок. Ключевым элементом синтаксиса является "/;" который идет в конце некоторого кода и сопровождается тестом. Некоторый пример кода (и вывод ниже).

Input:
Average[x_] := Mean[x] /; VectorQ[x, NumericQ]
Average[{1, 2, 3}]
Average[$Failed]

Output:
2
Average[$Failed]

Если тест проще, есть другой символ, который выполняет аналогичное тестирование шаблона "?" и идет сразу после аргумента в объявлении шаблона / функции. Другой пример ниже.

Input:
square[x_?NumericQ] := x*x
square[{1, 2, 3}]
square[3]

Output:
square[{1, 2, 3}]
9

То, что я хотел бы получить, - это способ определения общей процедуры для обнаружения распространения ошибок без необходимости радикально менять способ написания функций прямо сейчас, предпочтительно без добавления существенной типизации.

Вот попытка:

funcDef = t_[args___]  :c-:  a_ :> ReleaseHold[Hold[t[args] := 
                         Check[a, Print@Hold[a]; Abort[]]]];
Clear@v;
v[x_, y_] :c-: Sin[x/y] /. funcDef;
?v
v[2, 3]
v[2, 0] 

: C-: это, конечно, Esc c- Esc, неиспользуемый символ (\[CircleMinus]), но это подойдет любому.

Выход:

Global`v
v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]

Out[683]= Sin[2/3]

During evaluation of In[679]:= Power::infy: Infinite expression 1/0 encountered. >>

During evaluation of In[679]:= Hold[Sin[2/0]]

Out[684]= $Aborted

Что мы изменили

       v[x_, y_] := Sin[x/y]

от

       v[x_, y_] :c-: Sin[x/y] /. funcDef;  

Это почти удовлетворяет мои предпосылки.

редактировать

Возможно, также удобно добавить для функции "обнаженное" определение, которое не проходит проверку на ошибки. Мы можем изменить правило funcDef на:

funcDef = 
     t_[args___]  \[CircleMinus] a_ :> 

            {t["nude", args] := a, 

             ReleaseHold[Hold[t[args] := Check[a, Print@Hold[a]; Abort[]]]]
            };  

получить за

 v[x_, y_] :c-: Sin[x/y] /. funcDef;  

этот вывод

v[nude,x_,y_]:=Sin[x/y]

v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]

Это может помочь определить определение перехвата, чтобы подобрать условия ошибки и сообщить о них значимым образом:

f[x_?NumericQ] := x^2;
f[args___] := Throw[{"Bad Arguments: ", Hold[f[args]]}]

Так что ваши звонки верхнего уровня могут использовать Catch[], или вы можете просто позволить ему всплыть:

In[5]:= f[$Failed]

During evaluation of In[5]:= Throw::nocatch: Uncaught Throw[{Bad Args: ,Hold[f[$Failed]]}] returned to top level. >>

Out[5]= Hold[Throw[{"Bad Args: ", Hold[f[$Failed]]}]]
Другие вопросы по тегам