Pointless Prolog
This document proposes relative clauses as syntactic sugar for Prolog, which allow us to write more concisely by avoiding repetitively naming variables. Relative clauses are written as a list of predicates surrounded by curly braces. They introduce a new variable, which is constrained by applying all of the included predicates to it. For example:
{red, block}
is equivalent to the query
?- red(X), block(X).
When used as an argument to a predicate, the relative clause is replaced by the variable, and the constraints are attached as the body of a rule:
hold({big, red, block})
becomes
hold(X) :- big(X), red(X), block(X).
For convenience, the parentheses can be omitted when the relative clause is the only argument, ie. hold({block})
can be written as hold{block}
.
Predicates which take multiple arguments can be partially applied, in which case the variable is used as the remaining argument:
hold{block, supports(foo)}
⇓
hold(X) :- block(X), supports(foo, X).
By default, the last argument is considered to be the "remaining" one, but it can be moved to the first argument instead by putting an apostrophe after the predicate name:
hold{block, supports'(foo)}
⇓
hold(X) :- block(X), supports(X, foo).
Relative clauses can be nested:
hold{block, supports'{pyramid}}
⇓
hold(X) :- block(X), supports(X, Y), pyramid(Y).
The disjunction operator (;
) can also be used:
hold{green, cube; blue, pyramid}
⇓
hold(X) :- green(X), cube(X); blue(X), cube(X).
which is, as usual, equivalent to:
hold(X) :- green(X), cube(X).
hold(X) :- blue(X), cube(X).
The turnstile (:-
) can be used within a relative clause, in which case the predicates on the left are used as the head of a new rule:
{on(foo) :- table}
⇓
on(foo, X) :- table(X).
The order can be reversed by flipping the turnstile, ie. these two lines are equivalent:
{head :- body}
{body -: head}
Another example, with multiple heads:
{pyramid -: big, pointy}
⇓
big(X) :- pyramid(X).
pointy(X) :- pyramid(X).
Existentials and Skolemisation
If the body is empty, then the clause creates a new atom rather than a new variable:
{-: blue, table}
⇓
blue(a1). table(a1).
Unless it is nested inside another relative clause, in which case it creates a new functor parametrised by the variables corresponding to the outer clauses:
{block -: on{-: pyramid}}
⇓
pyramid(f1(X)) :- block(X).
on(f1(X), X) :- block(X).
For meta-predicates like findall/3
, constraints from relative clauses within it are used as the goal parameter:
solutions{findall{red, block}}
⇓
solutions(L) :- findall(X, (red(X), block(X)), L).
{-: pyramid, on'{table}}.
pyramid(a2).
on(a2, T) :- table(T).
{pyramid -: on'{table}}.
on(P,T) :- pyramid(P), table(T).
{box -: contains'{blue, pyramid; red, block}}.
contains(B,X) :-
box(B), (blue(X), pyramid(X); red(X), block(X)).
{blue, block -: on'{green, block -: on'{red, block -: on'{table}}}}.
on(B,G) :- blue(B), block(B), green(G), block(G).
on(G,R) :- green(G), block(G), red(R), block(R).
on(R,T) :- red(R), block(R), table(T).
{block, taller_than'{red, cube} -: in'{box}}.
in(X,B) :- block(X), taller_than(X,C), red(C), cube(C), box(B).
{smallest{findall{red, cube}} -: on'{blue, block}}.
on(S,B) :-
smallest(L,S), findall(R, (red(R), cube(R)), L),
blue(B), block(B).
{length{findall{block, left_of'{box}}}}
?- length(L, Answer),
findall(X, (block(X), left_of(X,B), box(B)), L).
{-: farmer, owns'{-: donkey}}
farmer(a3). donkey(a4). owns(a3, a4).
{farmer -: owns'{-: donkey}}
donkey(f2(X)) :- farmer(X).
owns(X, f2(X)) :- farmer(X).
{farmer -: owns'{donkey}}
owns(F,D) :- farmer(F), donkey(D).
{-: donkey, owns{farmer}}
donkey(a5).
owns(F, a5) :- farmer(F).
could something like aspi be made into a Prolog term/goal expansion library? or its semantics is too different for that to work cleanly and with full support of all the novelty?