Everything is a term: operators and DSLs
Surface syntax that denotes plain terms, op/3, and Prolog as the host of a small language.
Metaprogramming
Accordingly it is possible to read and reason about programs, to update or synthesise programs, and to execute programs, possibly in different conditions.
- In Java, reflection is a way of inspecting certain parts of a program, even at run-time; other libraries allow for so-called "code instrumentation".
- Scala has advanced techniques to manipulate and execute Scala programs (documentation).
Metaprogramming in Prolog
Very powerful, simple and direct: the clauses of a program are actually terms, even syntactically, and this paves the way for supporting virtually any metaprogramming technique. Especially, this is used in AI to tweak Prolog into a component of symbolic reasoning.
The ingredients of this and the next lessons:
- "everything is a term";
- libraries to "call" terms;
- dynamic theories;
- metainterpretation.
Everything is a term
File surface-syntax.pl. Surface syntax that actually denotes standard terms:
- goals have the same syntax as terms;
- binary infix operators, e.g.
X = 1denotes'='(X, 1). There are dozens:+,-,*,/,:-,==,;, ...; - unary prefix operators, e.g.
-Xdenotes'-'(X):+,-,$, ...; - list syntax:
[A,B,C]is'.'(A,'.'(B,'.'(C,'[]'))), and[A,B,C|T]is'.'(A,'.'(B,'.'(C,T))); - comma syntax (when used without functor or predicate):
(A,B,C,D)is','(A,','(B,','(C,D))), and(A)isA; - braces:
{A,B,C,D}is'{}'(','(A,','(B,','(C,D)))), and{p(X)}is'{}'(p(X)).
?- G = (X = 1), G =.. L?- T = (a + b * c), T =.. L?- T = -X, T =.. L?- (A, B, C) = ','(1, ','(2, 3))?- (a) == a?- {p(X)} = '{}'(p(X))?- {0, 1, 2+3} = '{}'(A), A =.. L?- [a, b|T] = '[|]'(a, '[|]'(b, T))The goal {0,1,2+3} = '{}'(A), A =.. L, gives A = (0,1,2+3) and L = [',', 0, (1, 2+3)]: the braces wrap a comma term, whose functor is ',', with two arguments, 0 and the comma term (1, 2+3).
Operators
File operators.pl. An operator is a predicate or functor that is declared to have a different syntax, that is, infix if 2-ary or prefix if 1-ary. It can also be a symbol without the need for quotes.
It must be defined by the predicate op/3, executed before it is used:
- the first argument is a number, describing the priority with respect to other operators (lower binds tighter);
- the second is a literal describing the syntax:
xfy,yfx,xfxfor infix,fx(andfy) for prefix, wherefis the operator andx,ythe arguments (x: strictly lower priority,y: lower or equal); - the third is the operator itself.
An operator as a predicate, here approximate equality of numbers:
% use as predicate
:- op(100, xfx, '~').
N1 ~ N2 :- N1 > N2, !, Delta is N1 - N2, Delta < 0.1.
N1 ~ N2 :- N2 ~ N1.
% use as functor
:- op(100, xfy, '::').
to_list(A :: B, [A | B2]) :- to_list(B, B2).
to_list(nil, []).?- 10 ~ 10.01?- to_list(10 :: 20 :: 30 :: nil, L)?- X = (10 :: 20 :: nil), X =.. L~is used as a predicate:10 ~ 10.01is a goal.::is used as a functor:10 :: 20 :: 30 :: nilis a term, a list-like structure, and because::isxfyit nests to the right, exactly like the list constructor.
Prolog as host for a DSL
File dsl.pl. Prefix operators let Prolog host a small domain-specific language for describing a person:
% use as DSL
:- op(100, fx, person).
:- op(100, fx, name).
:- op(100, fx, age).
:- op(100, fx, nationality).
:- op(100, fx, married).
person {
name 'Rossi Marco',
age 30,
nationality italy,
married false
}.?- person { name NM, age A, nationality N, married M }The fact is an ordinary Prolog clause: person is a prefix operator applied to a braces term holding comma-separated name ..., age ... entries. The goal uses variables in the same syntax, so unification extracts every field. Note the space in person {: written without it, a name followed by braces is read by SWI-Prolog as a dict.
Exercise: Your own operator
Declare an infix operator likes (priority 700, type xfx) and add the facts mia likes tea and zoe likes coffee written with it. Then mia likes X must give X = tea.