Defining "let expressions" in Prolog - prolog

In many functional programming languages, it is possible to "redefine" local variables using a let expression:
let example =
let a = 1 in
let a = a+1 in
a + 1
I couldn't find a built-in Prolog predicate for this purpose, so I tried to define a let expression in this way:
:- initialization(main).
:- set_prolog_flag(double_quotes, chars).
replace(Subterm0, Subterm, Term0, Term) :-
( Term0 == Subterm0 -> Term = Subterm
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],
maplist(replace(Subterm0,Subterm), Args0, Args),
Term =.. [F|Args]
).
let(A,B) :-
((D,D1) = (A1 is B1,C is B1);
(D,D1) = (A1=B1,C=B1)),
subsumes_term(D,A),
D=A,
replace(A1,C,B,B2),
call((D1,B2)).
main :- let(A = 1,(
writeln(A),
let(A is A+1,(
writeln(A),
let(A is A * 2,(
writeln(A)
))
))
)).
This implementation appears to incorrect, since some of the variables are bound before being replaced. I want to define an expression that would allow more than one variable to be "redefined" simultaneously:
main :- let((A = 1, B = 2), % this will not work with the let/2 predicate that I defined
let((A=B,B=A),(
writeln(A),
writeln(B)
))
).
Is it possible to implement a let expression in a way that allows several variables to be redefined at the same time?

The issue with defining let as a normal predicate is that you can't redefine variables that appear outside the outermost let. Here is my attempt at a more correct version, which uses goal expansion. (To me it makes sense, because as far as I know, in lisp-like languages, let cannot be defined as a function but it could be defined as a macro.)
%goal_expansion(let(Decl,OriginalGoal),Goal) :- %% SWI syntax
goal_expansion(let(Decl,OriginalGoal), _M, _, Goal, []) :- %%SICStus syntax
!,
expand_let(Decl,OriginalGoal,Goal).
expand_let(X, OriginalGoal, Goal) :-
var(X),
!,
replace(X,_Y,OriginalGoal,NewGoal),
Goal=(true,NewGoal).
expand_let(X is Decl, OriginalGoal, Goal) :-
var(X),
!,
replace(X,Y,OriginalGoal,NewGoal),
Goal=(Y is Decl,NewGoal).
expand_let(X = Decl, OriginalGoal, Goal) :-
var(X),
!,
replace(X,Y,OriginalGoal,NewGoal),
Goal=(Y = Decl,NewGoal).
expand_let([],OriginalGoal, Goal) :-
!,
Goal=OriginalGoal.
expand_let([L|Ls],OriginalGoal, Goal) :-
!,
expand_let_list([L|Ls],OriginalGoal,InitGoals,NewGoal),
Goal=(InitGoals,NewGoal).
expand_let((L,Ls),OriginalGoal, Goal) :-
!,
expand_let(Ls,OriginalGoal, SecondGoal),
expand_let(L,SecondGoal, Goal).
expand_let_list([],Goal,true,Goal).
expand_let_list([L|Ls],OriginalGoal,(Init,InitGoals),NewGoal):-
(
var(L)
->
replace(L,_,OriginalGoal,SecondGoal),
Init=true
;
L=(X=Decl)
->
replace(X,Y,OriginalGoal,SecondGoal),
Init=(Y=Decl)
;
L=(X is Decl)
->
replace(X,Y,OriginalGoal,SecondGoal),
Init=(Y is Decl)
),
expand_let_list(Ls,SecondGoal,InitGoals,NewGoal).
This is reusing the replace/4 predicate defined in the question. Note also that the hook predicate differs between Prolog versions. I am using SICStus, which defines goal_expansion/5. I had a quick look at the documentation and it seems that SWI-Prolog has a goal_expansion/2.
I introduced a different syntax for multiple declarations in a single let: let((X1,X2),...) defines X1, then defines X2 (so is equivalent to let(X1,let(X2,...))), while let([X1,X2],...) defines X1 and X2 at the same time (allowing the swap example).
Here are a few example calls:
test1 :- let(A = 1,(
print(A),nl,
let(A is A+1,(
print(A),nl,
let(A is A + 1,(
print(A),nl
))
))
)).
test2 :- A=2,let([A=B,B=A],(print(B),nl)).
test3 :- A=1, let((
A is A * 2,
A is A * 2,
A is A * 2
),(
print(A),nl
)),print(A),nl.
test4 :- let([A=1,B=2],let([A=B,B=A],(print(A-B),nl))).
test5 :- let((
[A=1,B=2],
[A=B,B=A]
),(
print(A-B),nl
)).

let is essentially a way of creating (inline to the source) a new, local context in which to evaluate functions (see also: In what programming language did “let” first appear?)
Prolog does not have "local contexts" - the only context is the clause. Variables names are only valid for a clause, and are fully visible inside the clause. Prolog is, unlike functional programs, very "flat".
Consider the main:
main :- let(A = 1,(
writeln(A),
let(A is A+1,(
writeln(A),
let(A is A * 2,(
writeln(A)
))
))
)).
Context being clauses, this is essentially "wrong pseudo code" for the following:
main :- f(1).
f(A) :- writeln(A), B is A+1, g(B).
g(A) :- writeln(A), B is A*2, h(B).
h(A) :- writeln(A).
?- main.
1
2
4
true.
The let doesn't really bring much to the table here. It seems to allow one to avoid having to manually relabel variables "on the right" of the is, but that's not worth it.
(Now, if there was a way of creating nested contexts of predicates to organize code I would gladly embrace that!).
Let's probe further for fun (and because I'm currently trying to implement the Monad Idiom to see whether that makes sense).
You could consider creating an explicit representation of the context of variable bindings, as if you were writing a LISP interpreter. This can be done easily with SWI-Prolog dicts, which are just immutable maps as used in functional programming. Now note that the value of a variable may become "more precise" as computation goes on, as long as it has any part that is still a "hole", which leads to the possibility of old, deep contexts getting modified by a current operation, not sure how to think about that.
First define the predicate to generate a new dict from an existing one, i.e. define the new context from the old one, then the code becomes:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.
twice_a(Din,Din.put(a,X)) :- X is Din.a * 2.
main :- f(_{a:1}).
f(D) :- writeln(D.a), inc_a(D,D2), g(D2).
g(D) :- writeln(D.a), twice_a(D,D2), h(D2).
h(D) :- writeln(D.a).
The A has gone inside the dict D which is weaved through the calls.
You can now write a predicate that takes a dict and the name of a
context-modifying predicate ModOp, does something that depends on the context (like calling writeln/1 with the value of a), then modifies the
context according to ModOp.
And then deploy foldl/4 working over a list, not of objects, but of operations, or rather, names of operations:
inc_a(Din,Din.put(a,X)) :- X is Din.a + 1.
twice_a(Din,Din.put(a,X)) :- X is Din.a * 2.
nop(Din,Din).
write_then_mod(ModOp,DictFromLeft,DictToRight) :-
writeln(DictFromLeft.a),
call(ModOp,DictFromLeft,DictToRight).
main :-
express(_{a:1},[inc_a,twice_a,nop],_DictOut).
express(DictIn,ModOps,DictOut) :-
foldl(
write_then_mod, % will be called with args in correct order
ModOps,
DictIn,
DictOut).
Does it work?
?- main.
1
2
4
true.
Is it useful? It's definitely flexible:
?- express(_{a:1},[inc_a,twice_a,twice_a,inc_a,nop],_DictOut).
1
2
4
8
9
_DictOut = _9368{a:9}.

This is how you would type this in using Prolog syntax:
example(X, Y) :-
X = 1,
succ(X, Y).
If it is something else you are trying to achieve, you need to explain better. "How do I type it in Prolog" comes strictly after "What am I doing?"
Or is it that you really want this kind of syntactic nesting in Prolog? Could you provide a couple of examples where you think it is beneficial?

It's possible to define a let predicate that recursively replaces nested let expressions, so that local variables can be "redefined" without being renamed. This is one way to implement it:
:- initialization(main).
:- set_prolog_flag(double_quotes, chars).
replace(Subterm0, Subterm, Term0, Term) :-
( Term0 == Subterm0 -> Term = Subterm
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],
maplist(replace(Subterm0,Subterm), Args0, Args),
Term =.. [F|Args]
).
replace_let(Term0, Term) :-
( [Term0,Term1] = [A,(A2 is B1, C2)],
(Pattern = (A1 is B1);Pattern = (A1 = B1)),
P1 = let(Pattern,C1),
subsumes_term(P1,A),
P1=A,
replace(A1,A2,C1,C2),
replace_let(Term1,Term)
; var(Term0) -> Term = Term0
; Term0 =.. [F|Args0],
maplist(replace_let, Args0, Args),
Term =.. [F|Args]
).
let(A,B) :- replace_let(let(A,B),C),call(C).
main :-
B = 3,
let(A is B+1,(
writeln(A),
let(A is A + 1,(
writeln(A),
C is A + 1,
let(A = C,(
writeln(A)
))
))
)).
This implementation still doesn't work with "simultaneous" variable definitions, but the replace/2 predicate could easily be modified to replace several variables simultaneously.

Related

Is there a way to do size comparison?

I have recently discovered the language Prolog and have been doing exercises on its basics. I am currently creating a database on animal classes like mammals, birds and reptiles, I want to expand the database by having a size comparison within the animals but not sure how.
Here is my database.
warm_blooded(bat).
warm_blooded(penguin).
cold_blooded(crocodile).
has_fur(bat).
has_feathers(penguin).
has_scales(crocodile).
gives_birth_live(bat).
lays_eggs(penguin).
lays_eggs(crocodile).
produces_milk(bat).
has_lungs(crocodile).
has_lungs(bat).
has_lungs(penguin).
%% if the being belongs to the mammalai class ,mammalia being the scientific word for mammal
mammalia(X) :-
warm_blooded(X),
produces_milk(X),
(
has_fur(X)
;
gives_birth_live(X)
),
format('~w ~s mammal ~n', [X, "is a"]).
%% if the being belongs to the aves class aves being the scientific word for bird
aves(X) :-
warm_blooded(X),
has_feathers(X),
lays_eggs(X),
has_lungs(X),
format('~w ~s bird ~n', [X, "is a"]).
%% if the being belongs to the reptillia class(reptillia being the scientific word for reptile
reptillia(X) :-
cold_blooded(X),
lays_eggs(X),
has_scales(X),
has_lungs(X),
format('~w ~s reptile ~n', [X, "is a"]).
I've tried adding sizes within the parameters but I keep getting compilation errors. I want to have an output wherein the user is able to determine which animal is bigger when compared with each other.
A simple an effective way is to just associate a size fact with each animal.
size(bat,1).
size(penguin,2).
size(crocodile,3).
Then add one predicate with two clauses to chose the larger of the two animals.
larger(A,B,A) :-
size(A,S1),
size(B,S2),
S1 > S2.
larger(A,B,B) :-
size(A,S1),
size(B,S2),
S2 >= S1.
Examples:
?- larger(penguin,crocodile,X).
X = crocodile.
?- larger(penguin,bat,X).
X = penguin ;
false.
?- larger(bat,bat,X).
X = bat.
Note that for examples where the the second animal is smaller, it tries the first clause and succeeds, but then has a choice point and so tries the second clause and fails. This is the pure solution.
If you want to use a cut to avoid the choice point, which is impure, you can do the following
larger_2(A,B,A) :-
size(A,S1),
size(B,S2),
S1 > S2,
!.
larger_2(A,B,B) :-
size(A,S1),
size(B,S2),
S2 >= S1,
!.
Examples:
?- larger_2(penguin,crocodile,X).
X = crocodile.
?- larger_2(penguin,bat,X).
X = penguin.
?- larger_2(bat,bat,X).
X = bat.
Another way as noted by Daniel Lyons is to use ->/2
larger_3(A,B,Larger) :-
size(A,SA),
size(B,SB),
(
SA > SB
->
Larger = A
;
Larger = B
).
This variation is not one operator of just ->/2 but a combination of both ->/2 and ;2.
This also does not leave a choice point and is impure because it too uses a cut (!). Using listing/1 we can see the implementation in Prolog.
?- listing('->'/2).
:- meta_predicate 0->0.
system:A->B :-
call(( A
-> B
)).
true.
?- listing(;/2).
:- meta_predicate 0;0.
system:A:B;A:C :- !,
call(A:(B;C)).
system:A:B;C:D :-
call(A:(B;C:D)).
true.
Notice the cut !.
How the two operators work together is noted in the SWI-Prolog documentation.
The combination ;/2 and ->/2 acts as if defined as:
If -> Then; _Else :- If, !, Then.
If -> _Then; Else :- !, Else.
If -> Then :- If, !, Then.
One other point to note about the use of ->/2 with ;/2 is that the syntactic layout among many Prolog programmers is to use () with the combination and offset the operators ->/2 and ;2 so that the ; stands out.
(
% condition
->
% true
;
% false
)
When a ; is used as an OR operator and not offset the ; is often overlooked in doing a quick scan of the source code as it is seen as a comma , instead of a ;.
Also note the absence of . or , after
SA > SB
and
Larger = A
and
Larger = B
but at the end an operator is needed,
).

Prolog addition on wrapped values

I wrote a test program with bindings (facts) between atoms and numbers.
bind(a, 3).
bind(b, 4).
bind(c, 5).
As part of a toy interpreter, I want to be able to perform additions on these atoms using Prolog's native arithmetic operators. For instance, I want to be able to run this query:
% val(X) is the value bound to X
?- X is val(a) + val(b).
X = 7.
However, I'm struggling to find a way to allow this addition. My first approach would have been this one:
% val(X, Y): Y is the value bound to X
val(X, Y) :- bind(X, Y).
% Make val an arithmetic function
:- arithmetic_function(val/1).
However, arithmetic_function/1 is no longer part of Prolog (or at least SWI-Prolog says it's deprecated), so I can't use it. Then I believed the best solution would be to overload the + operator to take this into account:
% val(X, Y): Y is the value bound to X
val(val(X), Y) :- bind(X, Y).
% Overload the + operator
+(val(_X, XVal), val(_Y, YVal)) :- XVal + YVal.
But here I've got my syntax all messed up because I don't really know how to overload a native arithmetic operation. When I type in the sample query from before, SWI-Prolog says ERROR: Arithmetic: ``val(a)' is not a function.
Would you have hints about a possible solution or a better approach or something I missed?
From the docs, I tought you should use function_expansion/3.
But I'm unable to get it to work, instead, goal_expansion could do, but isn't very attractive... for instance, if you save the following definitions in a file bind.pl (just to say)
:- module(bind, [test/0]).
:- dynamic bind/2.
bind(a, 3).
bind(b, 4).
bind(c, 5).
% :- multifile user:goal_expansion/2.
user:goal_expansion(val(X), Y) :- bind(X, Y).
user:goal_expansion(X is Y, X is Z) :- expand_goal(Y, Z).
user:goal_expansion(X + Y, U + V) :- expand_goal(X, U), expand_goal(Y, V).
test :-
X is val(a) + val(b), writeln(X).
and consult it, you can run your test:
?- test.
7
edit
after Paulo suggestion, here is an enhanced solution, that should work for every binary expression.
user:goal_expansion(X is Y, X is Z) :- expr_bind(Y, Z).
expr_bind(val(A), V) :- !, bind(A, V).
expr_bind(X, Y) :-
X =.. [F, L, R], % get operator F and Left,Right expressions
expr_bind(L, S), % bind Left expression
expr_bind(R, T), % bind Right expression
Y =.. [F, S, T]. % pack bound expressions back with same operator
expr_bind(X, X). % oops, I forgot... this clause allows numbers and variables
having defined user as target module for goal_expansion, it works on the CLI:
?- R is val(a)*val(b)-val(c).
R = 7.
edit
now, let's generalize to some other arithmetic operators, using the same skeleton expr_bind uses for binary expressions:
user:goal_expansion(X, Y) :-
X =.. [F,L,R], memberchk(F, [is, =<, <, =:=, >, >=]),
expr_bind(L, S),
expr_bind(R, T),
Y =.. [F, S, T].
and unary operators (I cannot recall no one apart minus, so I show a simpler way than (=..)/2):
...
expr_bind(-X, -Y) :- expr_bind(X, Y).
expr_bind(X, X).
Now we get
?- -val(a)*2 < val(b)-val(c).
true.
One way to do it is using Logtalk parametric objects (Logtalk runs on SWI-Prolog and 11 other Prolog systems; this makes this solution highly portable). The idea is to define each arithmetic operation as a parametric object that understands an eval/1 message. First we define a protocol that will be implemented by the objects representing the arithmetic operations:
:- protocol(eval).
:- public(eval/1).
:- end_protocol.
The basic parametric object understands val/1 and contains the bind/2 table:
:- object(val(_X_), implements(eval)).
eval(X) :-
bind(_X_, X).
bind(a, 3).
bind(b, 4).
bind(c, 5).
:- end_object.
I exemplify here only the implementation for arithmetic addition:
:- object(_X_ + _Y_, implements(eval)).
eval(Result) :-
_X_::eval(X), _Y_::eval(Y),
Result is X + Y.
:- end_object.
Sample call (assuming the entities above are saved in an eval.lgt file):
% swilgt
...
?- {eval}.
% [ /Users/pmoura/Desktop/eval.lgt loaded ]
% (0 warnings)
true.
?- (val(a) + val(b))::eval(R).
R = 7.
This can be an interesting solution if you plan to implement more functionality other than expression evaluation. E.g. a similar solution but for symbolic differentiation of arithmetic expressions can be found at:
https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/symdiff
This solution will also work in the case of runtime generated expressions (term-expansion based solutions usually only work at source file compile time and at the top-level).
If you're only interested in expression evaluation, Capelli's solution is more compact and retains is/2 for evaluation. It can also be made more portable if necessary using Logtalk's portable term-expansion mechanism (but note the caveat in the previous paragraph).
This is perhaps not exactly what I was looking for, but I had an idea:
compute(val(X) + val(Y), Out) :-
bind(X, XVal),
bind(Y, YVal),
Out is XVal + YVal.
Now I can run the following query:
?- compute(val(a) + val(c), Out).
Out = 8.
Now I need to define compute for every arithmetic operation I'm interested in, then get my interpreter to run expressions through it.

Simplify Expressions in Prolog

I wanted to ask how I can simplify expressions like:
1+2+a*5+0/b-c*0
= 3+a*5
And especially how can I separate such expressions in lists.
It's actually kind of fun in Prolog, because you don't need to do anything too magical to make it work.
?- X-Y = 1+2+a*5+0/b-c*0.
X = 1+2+a*5+0/b,
Y = c*0.
So you could start by doing something like this:
simplify(C, C) :- atom(C) ; number(C).
simplify(X+Y, X1+Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X*Y, X1*Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X/Y, X1/Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X-Y, X1-Y1) :- simplify(X, X1), simplify(Y, Y1).
This is an identity transform: it doesn't do anything.
?- simplify(1+2+a*5+0/b-c*0, Result).
Result = 1+2+a*5+0/b-c*0.
Now you can add rules for specific cases:
simplify(X*0, 0).
simplify(0*X, 0).
Now you get multiple results:
?- simplify(1+2+a*5+0/b-c*0, Result).
Result = 1+2+a*5+0/b-c*0 ;
Result = 1+2+a*5+0/b-0 ;
You could add a rule for constant folding:
simplify(X+Y, C) :- number(X), number(Y), C is X+Y.
You know, just have fun with it.
Lists aren't really any easier to work with, but you can make them using the "univ" operator: =..:
?- 1+2+a*5+0/b-c*0 =.. R.
R = [-, 1+2+a*5+0/b, c*0].
It's possible to simplify expressions in Prolog using unification, but this sometimes leads to unexpected results. In this example, two different variables in an expression are unified when "matching" a pattern, even if they were intended to be distinct:
:- initialization(main).
simplify(A+A,2*A).
main :-
simplify(A+B,C),
writeln(C).
In this case, simplify(A+B,C) would unify A with B.
To solve this problem, I use subsumes_term/2 to match a pattern without unifying the variables in an expression. subsumes_term(A+A,Input) will not match A+B unless A is already unified with B:
simplify(Input,2*A) :-
subsumes_term(A+A,Input).
This subsumes_term/2 predicate is often useful for metaprogramming: I used it to write a Prolog-to-Minizinc compiler.

How to define (and name) the corresponding safe term comparison predicates in ISO Prolog?

Standard term order (ISO/IEC 13211-1 7.2 Term order) is defined over all terms — including variables. While there are good uses for this — think of the implementation of setof/3, this makes many otherwise clean and logical uses of the built-ins in 8.4 Term comparison a declarative nightmare with imps (short form for imperative constructs) all around. 8.4 Term comparison features:
8.4 Term comparison
8.4.1 (#=<)/2, (==)/2, (==)/2, (#<)/2, (#>)/2,
(#>=)/2.
8.4.2 compare/3.
8.4.3 sort/2.
8.4.4 keysort/2.
To give an example, consider:
?- X #< a.
true.
This succeeds, because
7.2 Term order
An ordering term_precedes (3.181) defines whether or
not a term X term-precedes a term Y.
If X and Y are identical terms then X term_precedes Y
and Y term_precedes X are both false.
If X and Y have different types: X term_precedes Y iff the
type of X precedes the type of Y in the following order:
variable precedes floating point precedes integer
precedes atom precedes compound.
NOTE — Built-in predicates which test the ordering of terms
are defined in 8.4.
...
And thus all variables are smaller than a. But once X is instantiated:
?- X #< a, X = a.
X = a.
the result becomes invalid.
So that is the problem. To overcome this, one might either use constraints, or stick to core behavior only and therefore produce an instantiation_error.
7.12.2 Error classification
Errors are classified according to the form of Error_term:
a) There shall be an Instantiation Error when an
argument or one of its components is a variable, and an
instantiated argument or component is required. It has
the form instantiation_error.
In this manner we know for sure that a result is well defined as long as no instantiation error occurs.
For (\==)/2, there is already either dif/2 which uses constraints or dif_si/2 (formerly iso_dif/2) which produces a clean instantiation error.
dif_si(X, Y) :-
X \== Y,
( X \= Y -> true
; throw(error(instantiation_error,dif_si/2))
).
So what my question is about: How to define (and name) the corresponding safe term comparison predicates in ISO Prolog? Ideally, without any explicit term traversal. Maybe to clarify: Above dif_si/2 does not use any explicit term traversal. Both (\==)/2 and (\=)/2 traverse the term internally, but the overheads for this are extremely low compared to explicit traversal with (=..)/2 or functor/3, arg/3.
iso_dif/2 is much simpler to implement than a comparison:
The built-in \= operator is available
You now exactly what arguments to provide to\=
Definition
Based on your comments, the safe comparison means that the order won't change if variables in both subterms are instanciated. If we name the comparison lt, we have for example:
lt(a(X), b(Y)) : always holds for all any X and Y, because a #< b
lt(a(X), a(Y)) : we don't know for sure: intanciation_error
lt(a(X), a(X)) : always fails, because X #< X fails
As said in the comments, you want to throw an error if, when doing a side-by-side traversing of both terms, the first (potentially) discriminating pair of terms contains:
two non-identical variables (lt(X,Y))
a variable and a non-variable (lt(X,a), or lt(10,Y))
But first, let's review the possible approaches that you don't want to use:
Define an explicit term-traversal comparison function. I known you'd prefer not to, for performance reason, but still, this is the most straightforward approach. I'd recommend to do it anyway, so that you have a reference implementation to compare against other approaches.
Use constraints to have a delayed comparison: I don't know how to do it using ISO Prolog, but with e.g. ECLiPSe, I would suspend the actual comparison over the set of uninstanciated variables (using term_variables/2), until there is no more variables. Previously, I also suggested using the coroutine/0 predicate, but I overlooked the fact that it does not influence the #< operator (only <).
This approach does not address exactly the same issue as you describe, but it is very close. One advantage is that it does not throw an exception if the eventual values given to variables satisfy the comparison, whereas lt throws one when it doesn't know in advance.
Explicit term traversal (reference implementation)
Here is an implementation of the explicit term traversal approach for lt, the safe version of #<.
Please review it to check if this is what you expect. I might have missed some cases. I am not sure if this is conform to ISO Prolog, but that can be fixed too, if you want.
lt(X,Y) :- X == Y,!,
fail.
lt(X,Y) :- (var(X);var(Y)),!,
throw(error(instanciation_error)).
lt(X,Y) :- atomic(X),atomic(Y),!,
X #< Y.
lt([XH|XT],[YH|YT]) :- !,
(XH == YH ->
lt(XT,YT)
; lt(XH,YH)).
lt(X,Y) :-
functor(X,_,XA),
functor(Y,_,YA),
(XA == YA ->
X =.. XL,
Y =.. YL,
lt(XL,YL)
; XA < YA).
(Edit: taking into account Tudor Berariu's remarks: (i) missing var/var error case, (ii) order by arity first; moreover, fixing (i) allows me to remove subsumes_term for lists. Thanks.)
Implicit term traversal (not working)
Here is my attempt to achieve the same effect without destructuring terms.
every([],_).
every([X|L],X) :-
every(L,X).
lt(X,Y) :-
copy_term(X,X2),
copy_term(Y,Y2),
term_variables(X2,VX),
term_variables(Y2,VY),
every(VX,1),
every(VY,0),
(X #< Y ->
(X2 #< Y2 ->
true
; throw(error(instanciation_error)))
; (X2 #< Y2 ->
throw(error(instanciation_error))
; false)).
Rationale
Suppose that X #< Y succeeds.
We want to check that the relation does not depend on some uninitialized variables.
So, I produce respective copies X2 and Y2 of X and Y, where all variables are instanciated:
In X2, variables are unified with 1.
In Y2, variables are unified with 0.
So, if the relation X2 #< Y2 still holds, we know that we don't rely on the standard term ordering between variables. Otherwise, we throw an exception, because it means that a 1 #< 0 relation, that previously was not occuring, made the relation fail.
Shortcomings
(based on OP's comments)
lt(X+a,X+b) should succeed but produce an error.
At first sight, one may think that unifying variables that occur in both terms with the same value, say val, may fix the situation. However, there might be other occurences of X in the compared terms where this lead to an errorneous judgment.
lt(X,3) should produce an error but succeeds.
In order to fix that case, one should unify X with something that is greater than 3. In the general case, X should take a value that is greater than other any possible term1. Practical limitations aside, the #< relation has no maximum: compound terms are greater than non-compound ones, and by definition, compound terms can be made arbitrarly great.
So, that approach is not conclusive and I don't think it can be corrected easily.
1: Note that for any given term, however, we could find the locally maximal and minimal terms, which would be sufficient for the purpose of the question.
Third try! Developed and tested with GNU Prolog 1.4.4.
Exhibit 'A': "as simple as it gets"
lt(X,Y) :-
X \== Y,
( X \= Y
-> alpha_omega(Alpha,Omega),
term_variables(X+Y,Vars), % A
\+ \+ (label_vars(Vars,Alpha,Omega), X #< Y),
( \+ (label_vars(Vars,Alpha,Omega), X #> Y)
-> true
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
Exhibit 'B': "no need to label all vars"
lt(X,Y) :-
X \== Y,
( X \= Y
-> alpha_omega(Alpha,Omega),
term_variables(X,Xvars), % B
term_variables(Y,Yvars), % B
vars_vars_needed(Xvars,Yvars,Vars), % B
\+ \+ (label_vars(Vars,Alpha,Omega), X #< Y),
( \+ (label_vars(Vars,Alpha,Omega), X #> Y)
-> true
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
vars_vars_needed([], [], []).
vars_vars_needed([A|_], [], [A]).
vars_vars_needed([], [B|_], [B]).
vars_vars_needed([A|As],[B|Bs],[A|ABs]) :-
( A \== B
-> ABs = [B]
; vars_vars_needed(As,Bs,ABs)
).
Some shared code:
alpha_omega(Alpha,Omega) :-
Alpha is -(10.0^1000), % HACK!
functor(Omega,z,255). % HACK!
label_vars([],_,_).
label_vars([Alpha|Vs],Alpha,Omega) :- label_vars(Vs,Alpha,Omega).
label_vars([Omega|Vs],Alpha,Omega) :- label_vars(Vs,Alpha,Omega).
This is not a completely original answer, as it builds on #coredump's answer.
There is one type of queries lt/2 (the reference implementation doing explicit term traversal) fails to answer correctly:
| ?- lt(b(b), a(a,a)).
no
| ?- #<(b(b), a(a,a)).
yes
The reason is that the standard order of terms considers the arity before comparing functor names.
Second, lt/2 does not always throw an instatiation_error when it comes to comparing variables:
| ?- lt(a(X), a(Y)).
no
I write here another candidate for a reference explicit implementation:
lt(X,Y):- var(X), nonvar(Y), !, throw(error(instantiation_error)).
lt(X,Y):- nonvar(X), var(Y), !, throw(error(instantiation_error)).
lt(X,Y):-
var(X),
var(Y),
( X \== Y -> throw(error(instatiation_error)) ; !, false).
lt(X,Y):-
functor(X, XFunc, XArity),
functor(Y, YFunc, YArity),
(
XArity < YArity, !
;
(
XArity == YArity, !,
(
XFunc #< YFunc, !
;
XFunc == YFunc,
X =.. [_|XArgs],
Y =.. [_|YArgs],
lt_args(XArgs, YArgs)
)
)
).
lt_args([X1|OtherX], [Y1|OtherY]):-
(
lt(X1, Y1), !
;
X1 == Y1,
lt_args(OtherX, OtherY)
).
The predicate lt_args(Xs, Ys) is true when there is a pair of corresponding arguments Xi, Yi such that lt(Xi, Yi) and Xj == Yj for all the previous pairs Xj, Yj (for example lt_args([a,X,a(X),b|_], [a,X,a(X),c|_]) is true).
Some example queries:
| ?- lt(a(X,Y,c(c),_Z1), a(X,Y,b(b,b),_Z2)).
yes
| ?- lt(a(X,_Y1,c(c),_Z1), a(X,_Y2,b(b,b),_Z2)).
uncaught exception: error(instatiation_error)
What the heck! I'll give it a shot, too!
lt(X,Y) :-
X \== Y,
( X \= Y
-> term_variables(X,Xvars),
term_variables(Y,Yvars),
list_vars_excluded(Xvars,Yvars,XonlyVars),
list_vars_excluded(Yvars,Xvars,YonlyVars),
_ = s(T_alpha),
functor(T_omega,zzzzzzzz,255), % HACK!
copy_term(t(X,Y,XonlyVars,YonlyVars),t(X1,Y1,X1onlyVars,Y1onlyVars)),
copy_term(t(X,Y,XonlyVars,YonlyVars),t(X2,Y2,X2onlyVars,Y2onlyVars)),
maplist(=(T_alpha),X1onlyVars), maplist(=(T_omega),Y1onlyVars),
maplist(=(T_omega),X2onlyVars), maplist(=(T_alpha),Y2onlyVars),
% do T_alpha and T_omega have an impact on the order?
( compare(Cmp,X1,Y1),
compare(Cmp,X2,Y2)
-> Cmp = (<) % no: demand that X #< Y holds
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
Some more auxiliary stuff:
listHasMember_identicalTo([X|Xs],Y) :-
( X == Y
-> true
; listHasMember_identicalTo(Xs,Y)
).
list_vars_excluded([],_,[]).
list_vars_excluded([X|Xs],Vs,Zs) :-
( listHasMember_identicalTo(Vs,X)
-> Zs = Zs0
; Zs = [X|Zs0]
),
list_vars_excluded(Xs,Vs,Zs0).
Let's have some tests (with GNU Prolog 1.4.4):
?- lt(a(X,Y,c(c),Z1), a(X,Y,b(b,b),Z2)).
yes
?- lt(a(X,Y,b(b,b),Z1), a(X,Y,c(c),Z2)).
no
?- lt(a(X,Y1,c(c),Z1), a(X,Y2,b(b,b),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X,Y1,b(b,b),Z1), a(X,Y2,c(c),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(b(b), a(a,a)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X, 3).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X+a, X+b).
yes
?- lt(X+a, Y+b).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), b(Y)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), a(X)).
no
Edit 2015-05-06
Changed the implementation of lt/2 to use T_alpha and T_omega, not two fresh variables.
lt(X,Y) makes two copies of X (X1 and X2) and two copies of Y (Y1 and Y2).
Shared variables of X and Y are also shared by X1 and Y1, and by X2 and Y2.
T_alpha comes before all other terms (in X1, X2, Y1, Y2) w.r.t. the standard order.
T_omega comes after all other terms in the standard order.
In the copied terms, the variables that are in X but not in Y (and vice versa) are unified with T_alpha / T_omega.
If this has an impact on term ordering, we cannot yet decide the ordering.
If it does not, we're done.
Now, the counterexample given by #false works:
?- lt(X+1,1+2).
uncaught exception: error(instantiation_error,lt/2)
?- X=2, lt(X+1,1+2).
no
Here is a sketch of what I believe might be a working approach. Consider the goal lt(X, Y) and term_variables(X, XVars), term_variables(Y, YVars).
The purpose of the definition is to determine whether or not a further instantiation might change the term order (7.2). So we might want to find out the responsible variables directly. Since term_variables/2 traverses a term in the very same way that is of relevance to term order, the following holds:
If there is an instantiation that changes the term order, then the variables that have to be instantiated to witness that change are in the list prefixes XCs, YCs of XVars and YVars respectively, and either
XCs, YCs, XVars, and YVars are identical, or
XCs and YCs are identical up to the last element, or
XCs and YCs are identical up to the end where one list has a further element, and the other list is identical to its corresponding variable list XVars or YVars.
As an interesting special case, if the first elements in XVars and YVars differ, then those are the only variables to be tested for relevance. So this includes the case where there is no common variable, but it is even more general than that.
Next! This should do better than my previous attempt:
lt(X,Y) :-
X \== Y,
( X \= Y
-> term_variables(X,Xvars),
term_variables(Y,Yvars),
T_alpha is -(10.0^1000), % HACK!
functor(T_omega,z,255), % HACK!
copy_term(t(X,Y,Xvars,Yvars),t(X1,Y1,X1vars,Y1vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X2,Y2,X2vars,Y2vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X3,Y3,X3vars,Y3vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X4,Y4,X4vars,Y4vars)),
maplist(=(T_alpha),X1vars), maplist(maybe_unify(T_omega),Y1vars),
maplist(=(T_omega),X2vars), maplist(maybe_unify(T_alpha),Y2vars),
maplist(=(T_omega),Y3vars), maplist(maybe_unify(T_alpha),X3vars),
maplist(=(T_alpha),Y4vars), maplist(maybe_unify(T_omega),X4vars),
% do T_alpha and T_omega have an impact on the order?
( compare(Cmp,X1,Y1),
compare(Cmp,X2,Y2),
compare(Cmp,X3,Y3),
compare(Cmp,X4,Y4),
-> Cmp = (<) % no: demand that X #< Y holds
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
The auxiliary maybe_unify/2 deals with variables occurring in both X and Y:
maybe_unify(K,X) :-
( var(X)
-> X = K
; true
).
Checking with GNU-Prolog 1.4.4:
?- lt(a(X,Y,c(c),Z1), a(X,Y,b(b,b),Z2)).
yes
?- lt(a(X,Y,b(b,b),Z1), a(X,Y,c(c),Z2)).
no
?- lt(a(X,Y1,c(c),Z1), a(X,Y2,b(b,b),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X,Y1,b(b,b),Z1), a(X,Y2,c(c),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(b(b), a(a,a)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X, 3).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X+a, X+b).
yes
?- lt(X+a, Y+b).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), b(Y)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), a(X)).
no
?- lt(X+1,1+2).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X+X+2,X+1+3). % NEW
uncaught exception: error(instantiation_error,lt/2)
In this answer we present the predicate safe_term_less_than/2, a monotonic analogue to the iso-prolog built-in predicate (#<)/2 (§8.4.1, "term less than"). Its main properties are:
Explicit traversal of recursive terms.
Based on prolog-coroutining facilities, in particular when/2.
The comparison may progress gradually:
"freeze" whenever instantiation is not sufficient
"wake up" whenever the instantiation of the most significant terms change
The current frontline of the comparison is represented as an explicit (LIFO) stack.
The current state is directly passed around the residual goals.
The following code has been developed and tested on sicstus-prolog version 4.3.2:
safe_term_less_than(L, R) :- % exported predicate
i_less_than_([L-R]).
Above definition of safe_term_less_than/2 is based on the following auxiliary predicates:
i_less_than_([L-R|LRs]) :-
Cond = (?=(L,R) ; nonvar(L),nonvar(R)),
when(Cond, i_lt_step_(L,R,LRs)).
i_lt_step_(L, R, LRs) :-
( L == R
-> i_less_than_(LRs)
; term_itype(L, L_type),
term_itype(R, R_type),
compare(Ord, L_type, R_type),
ord_lt_step_(Ord, L, R, LRs)
).
term_itype(V, T) :-
( var(V) -> throw(error(instantiation_error,_))
; float(V) -> T = t1_float(V)
; integer(V) -> T = t2_integer(V)
; callable(V) -> T = t3_callable(A,F), functor(V, F, A)
; throw(error(system_error,_))
).
ord_lt_step_(<, _, _, _).
ord_lt_step_(=, L, R, LRs) :-
( compound(L)
-> L =.. [_|Ls],
R =.. [_|Rs],
phrase(args_args_paired(Ls,Rs), LRs0, LRs),
i_less_than_(LRs0)
; i_less_than_(LRs)
).
args_args_paired([], []) --> [].
args_args_paired([L|Ls], [R|Rs]) --> [L-R], args_args_paired(Ls, Rs).
Sample queries:
| ?- safe_term_less_than(X, 3).
prolog:trig_nondif(X,3,_A,_B),
prolog:trig_or([_B,X],_A,_A),
prolog:when(_A,(?=(X,3);nonvar(X),nonvar(3)),user:i_lt_step_(X,3,[])) ?
yes
| ?- safe_term_less_than(X, 3), X = 4.
no
| ?- safe_term_less_than(X, 3), X = 2.
X = 2 ? ;
no
| ?- safe_term_less_than(X, a).
prolog:trig_nondif(X,a,_A,_B),
prolog:trig_or([_B,X],_A,_A),
prolog:when(_A,(?=(X,a);nonvar(X),nonvar(a)),user:i_lt_step_(X,a,[])) ? ;
no
| ?- safe_term_less_than(X, a), X = a.
no
| ?- safe_term_less_than(X+2, Y+1), X = Y.
no
In comparison to previous answers, we observe:
The "text volume" of residual goals appears kind of "bloated".
The query ?- safe_term_less_than(X+2, Y+1), X = Y. fails—just like it should!
This answer follows up on my previous one which presented safe_term_less_than/2.
What's next? A safe variant of compare/3—unimaginatively called scompare/3:
scompare(Ord, L, R) :-
i_scompare_ord([L-R], Ord).
i_scompare_ord([], =).
i_scompare_ord([L-R|Ps], X) :-
when((?=(L,R);nonvar(L),nonvar(R)), i_one_step_scompare_ord(L,R,Ps,X)).
i_one_step_scompare_ord(L, R, LRs, Ord) :-
( L == R
-> scompare_ord(LRs, Ord)
; term_itype(L, L_type),
term_itype(R, R_type),
compare(Rel, L_type, R_type),
( Rel \== (=)
-> Ord = Rel
; compound(L)
-> L =.. [_|Ls],
R =.. [_|Rs],
phrase(args_args_paired(Ls,Rs), LRs0, LRs),
i_scompare_ord(LRs0, Ord)
; i_scompare_ord(LRs , Ord)
)
).
The predicates term_itype/2 and args_args_paired//2 are the same as defined previously.

Prolog - unusual cons syntax for lists

I have come across an unfamiliar bit of Prolog syntax in Lee Naish's paper Higher-order logic programming in Prolog. Here is the first code sample from the paper:
% insertion sort (simple version)
isort([], []).
isort(A.As, Bs) :-
isort(As, Bs1),
isort(A, Bs1, Bs).
% insert number into sorted list
insert(N, [], [N]).
insert(N, H.L, N.H.L) :-
N =< H.
insert(N, H.LO, H.L) :-
N > H,
insert(N, LO, L).
My confusion is with A.As in isort(A.As, Bs) :-. From the context, it appears to be an alternate cons syntax for lists, the equivalent of isort([A|As], Bs) :-.
As well N.H.L appears to be a more convenient way to say [N|[H|L]].
But SWI Prolog won't accept this unusual syntax (unless I'm doing something wrong).
Does anyone recognize it? is my hypothesis correct? Which Prolog interpreter accepts that as valid syntax?
The dot operator was used for lists in the very first Prolog system of 1972, written in Algol-W, sometimes called Prolog 0. It is inspired by similar notation in LISP systems. The following exemple is from the paper The birth of Prolog by Alain Colmerauer and Philippe Roussel – the very creators of Prolog.
+ELEMENT(*X, *X.*Y).
+ELEMENT(*X, *Y.*Z) -ELEMENT(*X, *Z).
At that time, [] used to be NIL.
The next Prolog version, written in Fortran by Battani & Meloni, used cases to distinguish atoms and variables. Then DECsystem 10 Prolog introduced the square bracket notation replacing nil and X.Xs with [] and [X,..Xs] which in later versions of DECsystem 10 received [X|Xs] as an alternative. In ISO Prolog, there is only [X|Xs], .(X,Xs), and as canonical syntax '.'(X,Xs).
Please note that the dot has many different rôles in ISO Prolog. It serves already as
end token when followed by a % or a layout character like SPACE, NEWLINE, TAB.
decimal point in a floating point number, like 3.14159
graphic token char forming graphic tokens as =..
So if you are now declaring . as an infix operator, you have to be very careful. Both with what you write and what Prolog systems will read. A single additional space can change the meaning of a term. Consider two lists of numbers in both notations:
[1,2.3,4]. [5].
1 .2.3.4.[]. 5.[].
Please note that you have to add a space after 1. In this context, an additional white space in front of a number may change the meaning of your terms. Like so:
[1|2.3]. [4]. 5. [].
1 .2.3. 4.[]. 5. [].
Here is another example which might be even more convincing:
[1,-2].
1.(-2).[].
Negative numbers require round brackets within dot-lists.
Today, there is only YAP and XSB left that still offer infix . by default – and they do it differently. And XSB does not even recognize above dot syntax: you need round brackets around some of the nonnegative numbers.
You wrote that N.H.L appears to be a more convenient way to say [N|[H|L]]. There is a simple rule-of-thumb to simplify such expressions in ISO Prolog: Whenever you see within a list the tokens | and [ immediately after each other, you can replace them by , (and remove the corresponding ] on the right side). So you can now write: [N,H|L] which does not look that bad.
You can use that rule also in the other direction. If we have a list [1,2,3,4,5] we can use | as a "razor blade" like so: [1,2,3|[4,5]].
Another remark, since you are reading Naish's paper: In the meantime, it is well understood that only call/N is needed! And ISO Prolog supports call/1, call/2 up to call/8.
Yes, you are right, the dot it's the list cons infix operator. It's actually required by ISO Prolog standard, but usually hidden. I found (and used) that syntax some time ago:
:- module(eog, []).
:- op(103, xfy, (.)).
% where $ARGS appears as argument, replace the call ($ARGS) with a VAR
% the calle goes before caller, binding the VAR (added as last ARG)
funcs(X, (V, Y)) :-
nonvar(X),
X =.. W.As,
% identify meta arguments
( predicate_property(X, meta_predicate M)
% explicitly exclude to handle test(dcg)
% I'd like to handle this case in general way...
, M \= phrase(2, ?, ?)
-> M =.. W.Ms
; true
),
seek_call(As, Ms, Bs, V),
Y =.. W.Bs.
% look for first $ usage
seek_call([], [], _Bs, _V) :-
!, fail.
seek_call(A.As, M.Ms, A.Bs, V) :-
M #>= 0, M #=< 9, % skip meta arguments
!, seek_call(As, Ms, Bs, V).
seek_call(A.As, _, B.As, V) :-
nonvar(A),
A = $(F),
F =.. Fp.FAs,
( current_arithmetic_function(F) % inline arith
-> V = (PH is F)
; append(FAs, [PH], FBs),
V =.. Fp.FBs
),
!, B = PH.
seek_call(A.As, _.Ms, B.As, V) :-
nonvar(A),
A =.. F.FAs,
seek_call(FAs, Ms, FBs, V),
!, B =.. F.FBs.
seek_call(A.As, _.Ms, A.Bs, V) :-
!, seek_call(As, Ms, Bs, V).
:- multifile user:goal_expansion/2.
user:goal_expansion(X, Y) :-
( X = (_ , _) ; X = (_ ; _) ; X = (_ -> _) )
-> !, fail % leave control flow unchanged (useless after the meta... handling?)
; funcs(X, Y).
/* end eog.pl */
I was advised against it. Effectively, the [A|B] syntax it's an evolution of the . operator, introduced for readability.
OT: what's that code?
the code above it's my attempt to sweeten Prolog with functions. Namely, introduces on request, by means of $, the temporary variables required (for instance) by arithmetic expressions
fact(N, F) :-
N > 1 -> F is N * $fact($(N - 1)) ; F is 1.
each $ introduce a variable. After expansion, we have a more traditional fact/2
?- listing(fact).
plunit_eog:fact(A, C) :-
( A>1
-> B is A+ -1,
fact(B, D),
C is A*D
; C is 1
).
Where we have many expressions, that could be useful...
This syntax comes from NU-Prolog. See here. It's probably just the normal list functor '.'/2 redefined as an infix operator, without the need for a trailing empty list:
?- L= .(a,.(b,[])).
L = [a,b]
Yes (0.00s cpu)
?- op(500, xfy, '.').
Yes (0.00s cpu)
?- L = a.b.[].
L = [a,b]
Yes (0.00s cpu)

Resources