Logic programs by nature have a form of exceptions: fail . Failure of a goal in a conjunction terminates further evaluation of the conjunction. A disjunction plays the role of exception handling, catching the failure and invoking the second disjunct to `handle' it.

Failure as a form of exceptions is not sufficient. Unlike success (which is accompanied by the current substitution, with the bindings for logic variables), failure carries no information and has no flavors. Success comes in many forms; failure is singular. Often an informative failure and nuanced failure handling is desired. One example is parsing, which was the first and still is the compelling application of logic programming. The laconic NO as the parsing result is infuriatingly unhelpful, especially for large input. The error ought to be described and pin-pointed. Furthermore, some failures of parsing are provisional, indicating the need to try another parsing alternative. Some other parsing errors (e.g., of lexing) should be treated as definite and immediately reported to the user. As another example, an undefined predicate ought to be treated differently from other failures, and handled not by initiating backtracking but by trying to locate the missing predicate (e.g., search for a predicate with a similar spelling; attempt to load an external library; etc). For these reasons, Prolog systems from early on had an ad hoc exception handling. ISO Prolog codified these practices, as catch/3 and throw/1 predicates. Here is their specification (quoted from the SWI Prolog documentation):

catch(:Goal, +Catcher, :Recover) ``Behaves as call/1 if no exception is raised when executing Goal . If an exception is raised using throw/1 while Goal executes, and the Goal is the innermost goal for which Catcher unifies with the argument of throw/1 , all choice-points generated by Goal are cut, the system backtracks to the start of catch/3 while preserving the thrown exception term and Recover is called as in call/1 . The overhead of calling a goal through catch/3 is very comparable to call/1 . Recovery from an exception is much slower, especially if the exception-term is large due to the copying thereof.'' throw(+Exception) ``Raise an exception. The system looks for the innermost catch/3 ancestor for which Exception unifies with the Catcher argument of the catch/3 call. ISO demands throw/1| to make a copy of Exception , walk up the stack to a catch/3 call, backtrack and try to unify the copy of Exception with Catcher .''

We stress the discarding of choice points after an exception is raised. In the following example, the exception cuts the choice X=4 :

?- catch((X=1;X=2;throw(3);X=4),Z,true). X = 1 ; X = 2 ; Z = 3.

Kanren implements the catch/3 and throw/1 predicates with the ISO Prolog interface. However, choice points are not discarded when the exception is raised. Therefore, the exception term does not have to be copied, and exception recovery is cheap. Here is the illustration.

(run #f (q) (fresh (y) (catch (fresh (x) (conde (succeed (== x 1)) (succeed (== x 2)) (succeed (== x 3) (throw `(e1 5))) (succeed (== x 4) (throw `(e1 15))) (succeed (== x 5))) (== q `(no-exc ,x))) `(e1 ,y) (== q `(caught 1 ,y)))))

((no-exc 1) (no-exc 2) (caught 1 5) (caught 1 15) (no-exc 5))

The two ways of handling exceptions in non-deterministic programs are clearest to explain in Haskell terms, as two different ways to compose a non-determinism monad transformer such as ListT with an exception monad transformer such as Either err . (We use lists here only for the sake of example; strictly speaking, ListT is not fully a monad transformer.) Transforming an exception monad with ListT gives us the type Either err [a] . This type expresses either a list of choices Right [a] for the values of type a , or a definite exception Left err . This monad describes the ISO Prolog exception handling. The alternative is to transform the list monad with Either err , obtaining the type [Either err a] that expresses the list of choices for successful computations Right a and exceptions Left err , or the mixture of the two. Kanren implements a particular non-determinism monad LogicT . Transforming it with Either err is straightforward.

Hansei, a domain-specific probabilistic programming language embedded in OCaml, may be regarded as a logic programming system. Since OCaml has native exceptions, so does Hansei. Unlike Kanren, Hansei implements non-determinism directly, using the library of delimited continuations. Hansei supports both modes of exception handling, cutting choice points or preserving them. The direct-style implementation makes the difference lucid, reducing it to the dynamic scoping of an exception handler try relative to a `non-determinism handler' reify0 . If try dynamically scopes over reify0 , choice points are cut off. If an exception is caught and handled before it gets to reify0 , choice points are preserved. (Different exceptions in Hansei may have different behavior with respect to choice points). Here is an illustration.

open ProbM open Ptypes exception E of int let model () = let i = geometric 0.4 in if i = 3 || i = 4 then raise (E (100 + i)) else i (* val model : unit -> int = <fun> *)

The examples t1 and t2 below do the inference first. Since the geometric distribution is infinite, over the entire domain of natural numbers, we have to limit the depth of the exact inference over this model (see the first argument to reify_part ). Once the inference extends far enough to trigger the exception, the inference is aborted. (The function reify_part is a composition of reify0 and the function to traverse the reified tree and collect the results. We can insert exception handling into reify_part so to preserve the values collected before an exception is raised.)

type ('a,'b) either = Left of 'a | Right of 'b;; let t1 = try Right (reify_part (Some 2) model) with E x -> Left x (* Right [(0.4, 0); (0.24, 1)] *) let t2 = try Right (reify_part (Some 10) model) with E x -> Left x (* Left 103 *)

The alternative is to catch exceptions before reifying a non-deterministic computation. All choice points are preserved then.

let t3 = reify_part (Some 10) (fun () -> try model () with E x -> x) (* val t3 : (Ptypes.prob * int) list = [(0.4, 0); (0.24, 1); (0.144, 2); (0.031, 5); (0.019, 6); (0.01, 7); (0.007, 8); (0.004, 9); (0.086, 103); (0.052, 104)] *)