Mercury procedures may throw exceptions. Exceptions may be caught using the predicates defined in the ‘exception’ library module, or using try goals.
A ‘try’ goal has the following form:
try Params Goal then ThenGoal else ElseGoal catch Term -> CatchGoal ... catch_any CatchAnyVar -> CatchAnyGoal
Goal, ThenGoal, ElseGoal, CatchGoal, CatchAnyGoal must be valid goals.
Goal must have one of the following determinisms: ‘det’, ‘semidet’, ‘cc_multi’, or ‘cc_nondet’.
The non-local variables of Goal must not have an inst equivalent to ‘unique’ or ‘mostly_unique’ or ‘any’, unless they have the type ‘io.state’.
Params must be a valid list of zero or more try parameters.
The “then” part is mandatory. The “else” part is mandatory if Goal may fail; otherwise it must be omitted. There may be zero or more “catch” branches. The “catch_any” part is optional. CatchAnyVar must be a single variable.
The try parameter ‘io’ takes a single argument, which must be the name of a state variable prefixed by ‘!’; for example, ‘io(!IO)’. The state variable must have the type ‘io.state’, and be in scope of the try goal. The state variable is threaded through ‘Goal’, so it may perform I/O but cannot fail. If no ‘io’ parameter exists, ‘Goal’ may not perform I/O and may fail.
A try goal has determinism ‘cc_multi’.
On entering a try goal, Goal is executed. If it succeeds without throwing an exception, ThenGoal is executed. Any variables bound by Goal are visible in ThenGoal only. If Goal fails, then ElseGoal is executed.
If Goal throws an exception, the exception value is unified with each of the Terms in the “catch” branches in turn. On the first successful unification, the corresponding CatchGoal is executed (and other “catch” and “catch_any” branches ignored). Variables bound during the unification of the Term are in scope of the corresponding CatchGoal.
If the exception value does not unify with any of the terms in “catch” branches, and a “catch_any” branch is present, the exception is bound to CatchAnyVar and the CatchAnyGoal executed. CatchAnyVar is visible in the CatchAnyGoal only, and is existentially typed, i.e. it has type ‘some [T] T’.
Finally, if the thrown value did not unify with any “catch” term, and there is no “catch_any” branch, the exception is rethrown.
The declarative semantics of a try goal is:
(try  Goal then Then else Else catch CP1 -> CG1 catch CG2 -> CG2 ... catch_any CAV -> CAG ) <=> ( Goal, Then ; not Goal, Else ; some [Excp] ( Excp = CP1 -> CG1 ; Excp = CP2 -> CG2 ; ... ; Excp = CAV, CAG ) ).
If no ‘else’ branch is present, let ‘Else = fail’. If no ‘catch_any’ branch is present, let ‘CAG = fail’.
An example of a try goal that performs I/O is:
:- pred p_carefully(io::di, io::uo) is cc_multi. p_carefully(!IO) :- (try [io(!IO)] ( io.write_string("Calling p\n", !IO), p(Output, !IO) ) then io.write_string("p returned: ", !IO), io.write(Output, !IO), io.nl(!IO) catch S -> io.write_string("p threw a string: ", !IO), io.write_string(S, !IO), io.nl(!IO) catch 42 -> io.write_string("p threw 42\n", !IO) catch_any Other -> io.write_string("p threw something: ", !IO), io.write(Other, !IO), % Rethrow the value. throw(Other) ).