Next: Formal semantics, Previous: Type conversions, Up: Top [Contents]
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
, 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 CP2 -> CG2 … catch_any CAV -> CAG ) <=> ( Goal, Then ; not Goal, Else ; some [Excp] ( if Excp = CP1 then CG1 else if Excp = CP2 then CG2 else if … … else Excp = CAV, CAG ) ).
If no ‘else’ branch is present, then ‘Else = fail’. If no ‘catch_any’ branch is present, then ‘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) ).
One common use for exceptions is to check the input and throw an exception if it is invalid. It might be tempting to implement this with a predicate such as the following:
:- pred check_target(target::in) is det. check_target(Target) :- ( if ... then true else throw("invalid target") ).
This code warrants caution, however. Consider the following usage:
shoot(Target, !IO) :- check_target(Target), unsafe_shoot(Target, !IO).
Mercury may reorder conjunctions, which is (probably) not what the user intended in this case. Furthermore, Mercury may optimize away the call to ‘check_target/1’ entirely, since the mode-determinism assertion for a ‘det’ predicate with no outputs essentially states that it is equivalent to ‘true’.
The strict sequential semantics can be used to guarantee that these changes will not occur (see Formal semantics). However, we recommend implementing checks like these in the following way, to avoid depending on the choice of operational semantics:
shoot(Target0, !IO) :- check_target(Target0, Target), unsafe_shoot(Target, !IO). :- pred check_target(target::in, target::out) is det. check_target(Target0, Target) :- ( if ... then Target = Target0 else throw("invalid target") ).
Next: Formal semantics, Previous: Type conversions, Up: Top [Contents]