Next: , Previous: Type conversions, Up: Top   [Contents]


13 Exception handling

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)
    ).

Next: , Previous: Type conversions, Up: Top   [Contents]