Skip to content

Instantly share code, notes, and snippets.

@davidar
Last active March 11, 2022 10:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidar/e760d131f2753657df066116c55cf17e to your computer and use it in GitHub Desktop.
Save davidar/e760d131f2753657df066116c55cf17e to your computer and use it in GitHub Desktop.
Prolog relative clauses

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

Meta-predicates

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

Examples

A pyramid is on the table.

{-: pyramid, on'{table}}.
pyramid(a2).
on(a2, T) :- table(T).

Every pyramid is on the table.

{pyramid -: on'{table}}.
on(P,T) :- pyramid(P), table(T).

The box contains the blue pyramid and the red block.

{box -: contains'{blue, pyramid; red, block}}.
contains(B,X) :-
  box(B), (blue(X), pyramid(X); red(X), block(X)).

The blue block is on the green block, which is on the red block, which is on the table.

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

All blocks taller than the red cube are in the box.

{block, taller_than'{red, cube} -: in'{box}}.
in(X,B) :- block(X), taller_than(X,C), red(C), cube(C), box(B).

The smallest red cube is on the blue block.

{smallest{findall{red, cube}} -: on'{blue, block}}.
on(S,B) :-
  smallest(L,S), findall(R, (red(R), cube(R)), L),
  blue(B), block(B).

How many blocks are to the left of the box?

{length{findall{block, left_of'{box}}}}
?- length(L, Answer),
   findall(X, (block(X), left_of(X,B), box(B)), L).

A farmer owns a donkey.

{-: farmer, owns'{-: donkey}}
farmer(a3). donkey(a4). owns(a3, a4).

Every farmer owns a donkey.

{farmer -: owns'{-: donkey}}
donkey(f2(X)) :- farmer(X).
owns(X, f2(X)) :- farmer(X).

Every farmer owns every donkey.

{farmer -: owns'{donkey}}
owns(F,D) :- farmer(F), donkey(D).

There is a donkey that is owned by every farmer.

{-: donkey, owns{farmer}}
donkey(a5).
owns(F, a5) :- farmer(F).
@erikkaplun
Copy link

@davidar
Copy link
Author

davidar commented Jan 25, 2022

@erikkaplun not sure, I never got around to implementing this for prolog, though I did something similar for clingo with https://github.com/davidar/aspi (and mercury in another branch)

@erikkaplun
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment