Do not call this particular beast an "implication". Call it an "if-the-else" (maybe the "arrow"? 🤔) It really has scant to do with material implication (which is given by
:- of course). It's just a Prolog control construct.
-> becomes rapidly unreadable if the predicate is "long" and even has imbricated
->. Imbricated if-the-else in imperative languages is already difficult to digest (Minimize Nesting and all that), but Prolog's choice of making
; the primary functor instead of
-> and making the "else" case by default a failure makes this triply worse.
foo(X,Z) :- bar(X) -> alpha(X,Z) ; beta(Z).
consider writing this (or variations without
! or with the negated test removed):
foo(X,Z) :- bar(X),!,alpha(X,Z). foo(X,Z) :- \+ bar(X),!,beta(Z).
or use a helper meta-predicate (see also if_/3 further below):
ifthen(Cond,Cons) :- call(Cond) -> call(Cons) ; true.
tryit(X) :- ifthen((X > 0),(format("Yes, X is > 0: ~q\n",[X]))). ?- tryit(10). Yes, X is > 0: 10 true. ?- tryit(-1). true.
Switch statement based on ->
Consider the expression:
test1(A) -> exec1(A,B); test2(A) -> exec2(A,B); else(A,B)
test1(A) -> exec1(A,B) ; test2(A) -> exec2(A,B) ; else(A,B)
Is this the correct way to write a switch statement or do we need to add parentheses?
Here is how the expression is parsed:
Ex=(test1(A) -> exec1(A,B); test2(A) -> exec2(A,B); else(A,B)), write_canonical(Ex).
Graphically (by hand ... there must be program out there to do that):
┌─────── test1(A) ┌─── -> ┤ │ └─────── exec1(A,B) ; ─┤ │ ┌──────── test2(A) │ ┌─── -> ┤ │ │ └──────── exec2(A,B) └─── ; ┤ └──── else(A,B)
Note that the principal functor of the if-then-else is
; and not
->. That's some crazy design because this evidently clashes with the disjunction
Anyway, that's how the expression looks, but that's not how it is traversed (that is what is meant by _"the combined semantics of this syntactic construct as defined above is different from the simple nesting of the two individual construct"_)
Testing the evaluation:
mt(C) :- test1(C) -> exec1(C); test2(C) -> exec2(C); else(C). test1(C) :- member(t1,C), !,format("test1: succ\n"). test1(C) :- \+member(t1,C),!,format("test1: fail\n"),false. test2(C) :- member(t2,C), !,format("test2: succ\n"). test2(C) :- \+member(t2,C),!,format("test2: fail\n"),false. exec1(C) :- member(e1,C), !,format("exec1: succ\n"). exec1(C) :- \+member(e1,C),!,format("exec1: fail\n"),false. exec2(C) :- member(e2,C), !,format("exec2: succ\n"). exec2(C) :- \+member(e2,C),!,format("exec2: fail\n"),false. else(C) :- member(el,C), !,format("else: succ\n"). else(C) :- \+member(el,C),!,format("else: fail\n"),false.
% every test fails, then else is called (and succeeds) ?- mt([e1,e2,el]). test1: fail test2: fail else: succ true.
% test 2 succeeds, then exec2 is called (and succeeds) ?- mt([t2,e1,e2,el]). test1: fail test2: succ exec2: succ true.
% test 1 succeeds, then exec1 is called (and succeeds) ?- mt([t1,e1,e2,el]). test1: succ exec1: succ true.
-> on the Prolog Toplevel
Note that you can use ->/2 on the Prolog Toplevel, like any goal. (This is far from asking whether a Horn clause is implied by the program ... it's just a control construct, a way of saying "prove the left-hand side once; on success, prove the right-hand side with backtracking otherwise prove the else with backtracking")
a(1). b(10). b(11). c(100). c(101).
?- a(X) -> b(Y). X = 1, Y = 10 ; X = 1, Y = 11.
a(1). a(2). b(1).
?- a(X) -> b(X). X = 1.
THE GOD OF LOGIC IS DISPLEASED
a(X) -> b(X) should respond false:
a(2) is TRUE but
b(2) is FALSE.
Dirty tricks: Making a goal deterministic with ->
The "premiss" of ->/2 is not subject to backtracking. This can be advantageous if you want to suppress multiple answer. Alternatively (and far more clearly to the next one who reads the code), you could use once/1.
?- member(X,[1,2,3]) -> (write("yes")) ; true. yes X = 1.
But the "conclusion" is still subject to backtracking:
?- member(X,[1,2,3]) -> (write("yes");write("yeah")) ; true. yes X = 1 ; yeah X = 1.
If-Then-Else using high-order predicate
An interesting approach to "reify" the
-> by having a higher-order construct
if_(If_1, Then_0, Else_0)
where If_1, Then_0, Else_0 are goals, is described in the paper Indexing
This finally gives the correct feel of LISP-like function composition instead of a side order of extra syntax.
The "reification" refers to the fact that truth values are no longer returned by predicates to be handled by the Prolog Processor but represented as atoms handled via dedicated arguments. The actual predicate truth value signifies "computation success" only.
if_(If_1, Then_0, Else_0) :- call(If_1, T), ( T == true -> Then_0 ; T == false -> Else_0 ; nonvar(T) -> throw(error(type_error(boolean,T), type_error(call(If_1,T),2,boolean,T))) ; throw(error(instantiation_error,instantiation_error(call(If_1,T),2))) ).
If_1 is a predicate taking a truth value represented by atoms
false as third argument.
Good example of formatting for the if-then-else
From the file
jpl.pl of the Java Language Interface:
jpl_type_to_class(T, RefA) :- ( ground(T) -> ( jpl_class_tag_type_cache(RefB, T) -> true ; ( jpl_type_to_findclassname(T, FCN) % peculiar syntax for FindClass() -> jFindClass(FCN, RefB), % which caches type of RefB jpl_cache_type_of_ref(class([java,lang],['Class']), RefB) % 9/Nov/2004 bugfix (?) ), jpl_assert(jpl_class_tag_type_cache(RefB,T)) ), RefA = RefB ; throw_instantiation_error(jpl_type_to_class/2,es01) ).