9
$\begingroup$

I have read the docs for Catch, Throw, and friends multiple times, and I confess that I find them utterly incomprehensible...

Can someone tell me how one would port the following Python snippet to Mathematica?

def safeeval(fn, x, epsilon=sys.float_info.epsilon):
    try:
        # first attempt
        return fn(x)
    except Exception, first_exception:
        try:
            try:
                # second attempt
                return fn(x + epsilon)
            except:
                # third (and last) attempt
                return fn(x - epsilon)
        except:
            # give up!
            raise first_exception

In words: return fn(x), trapping any exceptions during the evaluation of fn(x). If there is an exception, cache it in first_exception, and return fn(x + epsilon), again trapping any exceptions. If there is again an exception (a second one), return fn(x - epsilon). Lastly, if there is again an exception (a third one), give up and re-raise first_exception.

A higher-level, less literal translation of the above would be this: return the first one of fn(x), fn(x + epsilon), and fn(x - epsilon) that evaluates without errors (silencing any errors that do occur along the way). If none of these expressions evaluates without errors, then proceed exactly as with the usual evaluation of fn(x) (emitting the appropriate error messages, etc.).


EDIT: OK, I finally came up with a solution that approximates what I described above, though I'm not sure it's anywhere close to optimal:

safeEval[fn_, x_, epsilon_:$MachineEpsilon]:= Module[{v, sentinel},
    v = Quiet[Check[fn[x],
                    Check[fn[x+epsilon],
                          Check[fn[x-epsilon],
                                sentinel]]]];
    If[v =!= sentinel, v, fn[x]]
]

I was (*ahem*) thrown off by the whole Catch, Throw nomenclature. (These functions now seem to me like something unnaturally tacked onto Mathematica to make it look more like other more widely known languages.)

$\endgroup$

3 Answers 3

6
$\begingroup$

I don't know Python, and there's one aspect of the Python code I don't get; but this does what the last paragraph describes.

safeeval[fn_, x_, epsilon_] :=
 ReleaseHold @ Catch @ Quiet @
    Check[
     fn[x],
     Check[
      ReleaseHold @ Catch @ Quiet @
         Check[
          fn[x + epsilon],
          Throw @ Hold[fn[x - epsilon]]],
      Throw @ Hold[fn[x]]]]

I don't see how to re-enter the first f[x] from the point of the last Throw without creating an infinite loop. That's the part of the Python code I don't understand: re-raising first_exception. It certainly looks easier in Python. Perhaps someone will come up with a more elegant solution.

Examples

First try succeeds

safeeval[# &, 0, 1]
(* 0 *)

Second try succeeds

safeeval[1/# &, 0, 1]
(* 1 *)

Third try succeeds

safeeval[1/(# (# - 1)) &, 0, 1]
(* 1/2 *)

Default case - all failed

safeeval[1/0 &, 0, 1]

Power::infy: Infinite expression 1/0 encountered. >>

(* ComplexInfinity *)

Edit: Cacheing the error messages

Applying one of the answers to this question, one can easily adapt the above to cache the error messages from the first evaluation of f[x] and avoid having to evaluate it twice.

Module[{messages = {}},
  clearMessages[] := messages = {};
  collectMessages[m_] := AppendTo[messages, m];
  printMessages[] := ReleaseHold @ messages;
  ];
safeeval[fn_, x_, epsilon_ : $MachineEpsilon] :=
 Module[{retval},
  ReleaseHold @ Catch @ Quiet @
     Check[
      clearMessages[];
      Internal`AddHandler["Message", collectMessages];
      retval = fn[x];
      Internal`RemoveHandler["Message", collectMessages];
      retval,
      Check[
       ReleaseHold @ Catch @ Quiet @
          Check[
           fn[x + epsilon],
           Throw @ Hold[fn[x - epsilon]]],
       Throw[Hold[printMessages[]; retval]]
       ]
      ]
  ]
$\endgroup$
6
  • 2
    $\begingroup$ I think this would more in accord with the Python code if it were written safeeval[fn_, x_, epsilon_:$MachineEpsilon] := ... $\endgroup$
    – m_goldberg
    Commented Aug 25, 2013 at 22:18
  • $\begingroup$ I thought of that, but since I don't know Python, I didn't want to assume what epsilon was. I hoped the OP knew about $MachineEpsilon already. But thanks anyway! (+1 on the comment. :) $\endgroup$
    – Michael E2
    Commented Aug 25, 2013 at 22:41
  • $\begingroup$ Thanks! I came up with a solution very similar to yours (see my EDIT), though I can't follow your use of Hold, ReleaseHold, etc. I guess that their role has something to do with preserving the whole Catch/Throw scheme? As I alluded to in my EDIT, this scheme seems like something shoehorned into Mathematica for basically PR purposes... $\endgroup$
    – kjo
    Commented Aug 25, 2013 at 22:45
  • 1
    $\begingroup$ @kjo The Hold is so that f[x] is not evaluated inside the Quiet. ReleaseHold removes the Hold after the Catch; at that point, f[x] will be evaluated, outside Quiet. == You're solution seems reasonable. One defect (IMO) of both of ours is that f[x] is evaluated twice in the worst case. $\endgroup$
    – Michael E2
    Commented Aug 25, 2013 at 23:40
  • 1
    $\begingroup$ @kjo See update. (The second half of your comment is what I meant about evaluating f[x] twice.) $\endgroup$
    – Michael E2
    Commented Aug 26, 2013 at 3:36
3
$\begingroup$

It looks like you already have your solution, and a rather elegant one at that, but perhaps what is yet lacking is a convenient method that does not require manually writing that nested structure.

We can use recursion. The basic form looks like this:

SetAttributes[errorTry, HoldAll]

errorTry[a_, b__] := Quiet @ Check[a, errorTry[b]]

errorTry[x_] := x

You would use it like this:

fn[1] := (1/0; 1)
fn[2] := (1/0; 2)
fn[3] := (1/0; 3)

errorTry[fn[1], fn[2], fn[3], fn[1]]
1

With this simple form you do have to specify the first expression twice, and it is also evaluated twice in the fall-through case. To address these issues we might use:

SetAttributes[errorTry2, HoldAll]

errorTry2[a_, b___] := Module[{x}, errorTry[x = a, b, x]]

Use:

errorTry2[fn[1], fn[2], fn[3]]

If you wish to return the error messages for fn[1] in fall-through you could either simply reevaluate that expression, or if that is costly you can cache. The simple approach would look something like this:

SetAttributes[{errorTry3, eT3}, HoldAll]

errorTry3[a_, b___] := eT3[a, b] /. eT3[] :> a

eT3[a_, b___] := Quiet @ Check[a, eT3[b]]

Use as above. Finally, the complete self-contained function with caching:

SetAttributes[errorTry4, HoldAll]

errorTry4[a_, b___] :=
  Module[{f, x, msg = {}},
    SetAttributes[f, HoldAll];
    f[i_, j___] := Quiet @ Check[i, f[j]];
    Block[{Message = AppendTo[msg, {##}] &}, x = a];
    If[msg === {}, x, f[b] /. f[] :> (Message @@@ msg; x)]
  ]

I hope this gives you all the options you could want.

$\endgroup$
2
  • $\begingroup$ There are a lot of very cool ideas in that reply. Thanks! $\endgroup$
    – kjo
    Commented Aug 26, 2013 at 13:02
  • $\begingroup$ @kjo You're welcome. $\endgroup$
    – Mr.Wizard
    Commented Aug 26, 2013 at 15:45
0
$\begingroup$

This is a simple implementation of Java try/catch/finally, translated in Try/Failure/Finally

(* Mathematica Package *)


BeginPackage["Language`"]
(* ----------------------------------------------------------------------- *)
(*
    Try/Failure/Finally
    Failed/$Return/$Failure

    Try[
        body, 
        Failure[form, cbody], 
        ...
        Finally[fbody]
    ] 

where

    Failed[tag] == Throw[$Failed, tag]

    Failed[c::m, args___] == Throw[$Failed, Hold[c::m, {args}]]

    It is possible modify the result generated in 'body' by assign a new
    value to the symbol '$return'. 

    With 'Throw[result, tag]', the result of the expression is 'result'
    (o '$Failed' if you use 'Failed[tag]').
        The value 'result' will be assigned to '$Return' 

    Try[]                   == Try[Null]
    Try[body, Failure[...]*, Finally[...]?]

    Failure[]               == Failure[_, Null]
    Failure[form]           == Failure[form, Null]
    Failure[form, ebody]

    Finally[]               == Finally[Null]             
    Finally[fbody]

    form: a pattern
    tag:  a expression
    body, ebody, fbody: a expression

    La valutazione procede come segue:

    1) viene valutato 'body' (Try[body, ...])
    2) se non ci sono eccezioni, il risultato viene assegnato a $Return
        3) se e' presente la sezione 'Finally[fbody]' viene valutato 'fbody'
        4) - se in 'fbody' non viene assegnato un nuovo valore a '$Return' e ritorna
         Null, allora il risultato e' il corrente valore di $Return
           - se in 'fbody' viene assegnato un nuovo valore a '$Return' e ritorna 
         Null, allora il risultato e' il nuovo valore di $Return
           - se 'fbody' ritorna un valore diverso da Null, allora questo e' il nuovo
             valore dell'espressione
        5) in caso di eccezione (generata con 'Throw[value, tag]' oppure 'Failure[tag]'
           che e' equivalente a 'Throw[$Failed, tag]'), il valore di '$Return' e'
           'value' o '$Failed'.
    6) vengono scandite le sezioni 'Failure[form, cbody]' alla ricerca della 
       prima in cui 'form' ha un match con 'tag':
       - se non viene trovata, l'eccezione viene propagata ma VIENE valutata la
         sezione 'Finally[fbody]' se presente
       - se viene trovata, viene valutato 'cbody', che segue le stesse regole 
         di 'fbody'
       - se e' presente la sezione 'Finally[fbody]' la valutazione procede
         come indicato al punto 4 
    7) l'esatta eccezione sollevata (tag) si puo' trovare nei blocchi 'Failure[]' 
       e 'Finally[]' nella variabile '$Failure'

    Le variabili '$Failure' e '$Return' usano 'dynamic scoping' ('Block[...]')

    In 'ebody' e' possibile utilizzare eventuali pattern definiti in 'form'
*)
(* ----------------------------------------------------------------------- *)

$Return::usage="Usable in 'body' on 'Failure[]' and 'Finally[]' to change the \
  return value, but 'body' MUST return 'Null'. Otherwise the new value returned \
  is the value of the body evaluation";

$Failure::usage="Contains the exception generated";

Try::usage="Try[body, Failure[form, ebody]*, Finally[fbody]?] execute 'body' and \
  'fbody' if present. 
  If 'body' generate a exception (using Throw[.., tag]) the next expression \
  evaluated is the first 'ebody' of the 'Failure[form, ebody]' list where 'form' \
  match 'tag'";

Failed::usage="Failed[tag] == Throw[$Failed, tag]\
      Failed[c::m, args___] === Throw[$Failed, Hold[c::m, {args}]]";

Failure::usage="Failure[tag, ebody] | Failure[tag] | Failure[]"

Finally::usage="Finally[fbody] | Finally[]";


(* ----------------------------------------------------------------------- *)
(*                                                                         *)
(* ----------------------------------------------------------------------- *)

Begin["`Private`"]

Unprotect[
   Try, Failure, Finally, 
   Failed, $Return, $Failure
];

(* ----------------------------------------------------------------------- *)
(*
    Try[]               == Try[Null]
    Try[body, Failure[...]*, Finally[...]?]

    Failure[]           == Failure[_, Null]
    Failure[form]       == Failure[form, Null]
    Failure[form, ebody]

    Finally[]           == Finally[Null]             
    Finally[fbody]

    form: un pattern
    tag:  un'espressione
*)
(* ----------------------------------------------------------------------- *)

SetAttributes[Try, HoldAll];
SetAttributes[Failed, HoldFirst];
SetAttributes[HandleFailures, HoldAll];


(* ----------------------------------------------------------------------- *)
(* Failed                                                                  *)
(* ----------------------------------------------------------------------- *)

Failed[fail_] := Throw[$Failed, Evaluate[fail]];

Failed[message:MessageName[category_, _], args___] :=
    With[{mh = Hold[message, {args}]}, 
        Message[message, args];
        Throw[$Failed, mh];
    ];


(* ----------------------------------------------------------------------- *)
(* Try                                                                     *)
(* ----------------------------------------------------------------------- *)

Try[body_:Null, Longest[failures___Failure], finally_Finally:Finally[]] :=
    Block[{$Return=Null, $Failure=Null}, 
        Catch[
            HandleBody[body], 
            _, 
            HandleFailures[#1, #2, Hold[failures]]&
        ];
        HandleFinally[finally];
        Return[$Return];
    ];


(* ----------------------------------------------------------------------- *)
(* HandleBody                                                              *)
(* ----------------------------------------------------------------------- *)

HandleBody[body_] := 
    With[{}, 
        $Return=Evaluate[body]
    ];


(* ----------------------------------------------------------------------- *)
(* HandleFailures                                                          *)
(* ----------------------------------------------------------------------- *)

(*
    Param failures: Hold[failure, ...]

    failure ::= Failure[]          == Failure[_, Null]
                Failure[form]      == Failure[form, Null]
                Failure[form, body]
*)
HandleFailures[return_, tag_, failures_]:=
    Module[{i, n=Length[failures]}, 
        $Return=return;
            $Failure=tag;
        For[i=1, i<=n, ++i, 
            With[{failure=failures[[i]]}, 
            If[Length[failure]==0, Return[$Return]];     (*Failure[]*)
            With[{form=failure[[1]]},               (*Failure[form, body?]*)
            If[MatchQ[tag, form], 
                Return[HandleFailure[tag, failure]];
            ]]];
        ];
        Throw[return, tag];
    ];

(*
    Failure[form]      == Failure[form, Null]
    Failure[form, body]
*)
HandleFailure[tag_, Failure[form_, body_:Null]] :=
    With[{return=tag/.form->body}, 
        If[return =!= Null, $Return=return];
            Return[$Return];
    ];


(* ----------------------------------------------------------------------- *)
(* HandleFinally                                                           *)
(* ----------------------------------------------------------------------- *)

(*
    Finally[]     == Finally[Null]
    Finally[body]
*)
HandleFinally[Finally[body_:Null]] :=
    With[{return=Evaluate[body]}, 
        If[return =!= Null, $Return=return];
            Return[$Return];
    ];


(* ----------------------------------------------------------------------- *)
(*                                                                         *)
(* ----------------------------------------------------------------------- *)

Protect[
   Try, Failure, Finally, 
   Failed, $Failure, $Return
];

End[]

Print["Language`TryCatch` Installed"]

EndPackage[]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.