Предотвращение лавины ошибок времени выполнения в Mathematica - PullRequest
13 голосов
/ 14 ноября 2010

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

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

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

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

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

Если для отладчика установлено значение «Break on Messages», это предотвращает лавину ошибок, хотя это выглядит какизлишне включать его все время

Ответы [ 5 ]

10 голосов
/ 14 ноября 2010

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

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

Как указал Samsdram , правильная печать ваших функций поможетотличная сделка.Не забывайте о форме : 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, как мне хотелось бы.)

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

Редактировать 2 : Леонид указал, как можно использовать 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}
8 голосов
/ 21 ноября 2010

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

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_] := ... /; ...
3 голосов
/ 14 ноября 2010

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

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]]}]]
3 голосов
/ 14 ноября 2010

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

Вот попытка:

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[]]
3 голосов
/ 14 ноября 2010

Проблема здесь по сути одна из типов.Одна функция выдает неправильный вывод (неправильный тип), который затем передается во многие последующие функции, что приводит к множеству ошибок.Хотя 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...