ArticlePDF Available

Logic Programming, Functional Programming, and Inductive Definitions

Authors:

Abstract

An attempt at unifying logic and functional programming is reported. As a starting point, we take the view that "logic programs" are not about logic but constitute inductive definitions of sets and relations. A skeletal language design based on these considerations is sketched and a prototype implementation discussed.
Logic Programming, Functional Programming,
and Inductive Definitions
Lawrence C. Paulson 1 and Andrew W. Smith 2
1 Computer Laboratory, University of Cambridge, Cambridge CB2 3QG, England
2 Harlequin Limited, Barrington, Cambridge CB2 5RG, England
2 Lawrence C. Paulson and Andrew W. Smith
Table of Contents
1 Introduction ............................... 3
2 Declarative programmers: realists versus purists ........ 3
2.1 Functional programming ...................... 4
2.2 Logic programming ......................... 4
3 Logic programs: first-order theories or inductive definitions? 5
3.1 Logic programs as first-order theories ............... 6
3.2 Logic programs as inductive definitions .............. 7
3.3 Least models and the Closed World Assumption ......... 8
3.4 Specification and verification of logic programs .......... 9
4 Extended logic programming languages .............. 11
4.1 Larger fragments of first-order logic ................ 11
4.2 Other work concerning inductive definitions ............ 12
4.3 The language Loglisp ....................... 13
4.4 A logic language with functions ................... 13
5 A Prototype of the language ..................... 16
5.1 Rewrite Rules ............................ 16
5.2 Type Declarations .......................... 18
5.3 The equality function ‘eq’ ...................... 19
5.4 Other features ............................ 19
6 Operation of the prototype ...................... 20
6.1 Unification .............................. 20
6.2 Example: An Eight Queens Program ................ 22
6.3 Example: A Propositional calculus theorem prover ........ 23
7 Conclusions ................................ 25
Logic Programming and Inductive Definitions 3
1 Introduction
The unification of logic and functional programming, like the Holy Grail, is
sought by countless people [6, 14]. In reporting our attempt, we first discuss the
motivation. We argue that logic programming is still immature, compared with
functional programming, because few logic programs are both useful and pure.
Functions can help to purify logic programming, for they can eliminate certain
uses of the cut and can express certain negations positively.
More generally, we suggest that the traditional paradigm logic program-
ming as first-order logic is seriously out of step with practice. We offer an
alternative paradigm. We view the logic program as an inductive definition of
sets and relations. This view explains certain uses of Negation as Failure, and
explains why most attempts to extend Prolog to larger fragments of first-order
logic have not been successful. It suggests a logic language with functions, in-
corporating equational unification.
We have implemented a prototype of this language. It is very slow, but com-
plete, and appear to be faster than some earlier implementations of narrowing.
Our experiments illustrate the programming style and shed light on the further
development of such languages.
2 Declarative programmers: realists versus purists
Logic Programming and Functional Programming are often lumped together
under the heading ‘Declarative Programming’. Ideally, a declarative program
simply specifies the problem what we want and the computer works out
how to do it.
Of course this is an oversimplification. For the declarative languages that
exist now, the problem description really is a program: not for any physical
machine, but for an abstract machine. A functional program defines a system
of rewriting rules that can evaluate a desired function. A logic program defines
a search space of problem reductions that can solve all instances of the desired
goal. The declarative program expresses the algorithm more abstractly than, say,
a Pascal program, but the means of expression are restrictive when regarded as
a specification language: even more so if we care about efficiency.
Users of declarative languages can be described as realists or purists:
Realists set out to write useful programs. While they value the declarative
reading, they are prepared to compromise it if necessary.
Purists set out to demonstrate their declarative paradigm, and perhaps its
application to program correctness and synthesis. Their programs are com-
pletely pure, regardless of the consequences for efficiency.
4 Lawrence C. Paulson and Andrew W. Smith
Realists and purists are equally worthy; they simply have different priorities.
Note that an impure program can be more readable than a pure one, while a
pure program can be more efficient than an impure one.
Let us compare functional programming and logic programming through
these concepts. For the purist view we can compare the presentations by David
Turner and Robert Kowalski at a special meeting of the Royal Society in London.
2.1 Functional programming
A purist functional programmer might use Miranda, Lazy ML, or Haskell: lazy
functional languages with no side-effects whatever. David Turner’s presentation
to the Royal Society includes quick sort, a topological sort, and a program to
find a Knight’s tour of the chess board [42]. He also gives some simple proofs
and program derivations. Bird and Wadler [7] give a fuller account of the purist
approach; they derive functional programs from formal specifications.
Purists avoid Lisp because of its imperative features. David Turner says [42,
page 53]:
It needs to be said very firmly that Lisp, at least as represented by the dialects
in common use, is not a functional language at all.
Lisp has been impure from the very start. Assignments and go to’s feature promi-
nently in the Lisp 1.5 Programmer’s Manual [32]. But that same book devotes
a chapter to a program for the propositional calculus (Wang’s Algorithm). This
is a substantial, purely functional program probably the first ever and it
is written in Lisp.
A realist functional programmer might use Lisp or ML. These languages sup-
port a functional style but do not enforce it. Abelson and Sussman [1] illustrate
the realist approach using Scheme, a dialect of Lisp. Many of their examples are
purely functional.
The realists and the purists share some common ground. Many Lisp and ML
programmers strive for a pure style, while many pure functional programs can
be executed with reasonable efficiency.
2.2 Logic programming
Kowalski illustrates the purist approach. His presentation to the Royal Soci-
ety emphasises the relationship between logic programs and specifications [28,
page 11]:
The only difference between a complete specification and a program is one of
efficiency.
Logic Programming and Inductive Definitions 5
As an example, Kowalski gives a specification of sorting. He then gives the
following sorting program:
sort(X,X) :- ordered(X).
sort(X,Y) :- I<J, X[I] > X[J], interchange(X,I,J,Z), sort(Z,Y).
Given X, the program computes the sorted version Y by repeatedly exchanging
some X[I] and X[J] that are out of order. The program is highly nondetermin-
istic: the condition X[I] > X[J] is the only constraint on I and J.
3
To regard
this as a useful sorting program we must further constrain I and J, to reduce
the search drastically. We also must find a compiler clever enough to execute
interchange(X,I,J,Z) without copying.
Perhaps it is unfair to bear down on this little example. But the literature of-
fers few others. Hogger [26] writes at length about pure logic programs, typically
to reverse a list or test for list membership. By comparison, the pure functional
programs in Bird and Wadler [7] perform α-β search, construct Huffman coding
trees, and print calendars.
Clocksin and Mellish [13] illustrate the realist approach. They teach Prolog
style using a wide variety of programs, with applications such as parsing. But
many of these involve logically meaningless (or ‘extralogical’) predicates.
For logic programming, the realists and purists are far apart. Programming
in a pure style is difficult. Existing Prolog systems do not even provide pure
Prolog as a subset. They use depth-first search (which is incomplete) and they
omit the occurs check (which can create circular data structures).
Pure logic programs can be written by translating functional programs into
clauses. But this is hardly logic programming: key aspects like backtracking are
lost. Logic programming is far more ambitious than functional programming,
which is why it has not reached a similar stage of maturity.
The widespread interest in extending Prolog stems mainly from purist prin-
ciples. Kowalski again [28, page 22]:
In the longer term, we need to develop improved logic programming languages,
which do not rely on extralogical features for the sake of efficiency.
3 Logic programs: first-order theories or inductive
definitions?
We need an improved logic programming paradigm, not just an improved lan-
guage, if pure logic programming is to become practical. So let us consider what
logic programming really means. We begin with the orthodox view and then
propose an alternative.
3 The subscripting in X[I] > X[J] abbreviates contains(X,I,U), contains(X,J,V),
U>V
6 Lawrence C. Paulson and Andrew W. Smith
3.1 Logic programs as first-order theories
Prolog is descended from Robinson’s resolution principle for proving theorems
in first-order logic [37]. Every clause in a pure Prolog program stands for a
first-order formula; we are programming in logic. To illustrate this orthodox
view, consider the traditional ‘family relationships’ example:
grandparent(X,Z) :- parent(X,Y), parent(Y,Z).
cousin(X,Y) :- grandparent(Z,X), grandparent(Z,Y).
parent(elizabeth,charles).
parent(elizabeth,andrew).
parent(charles,william).
parent(charles,henry).
parent(andrew,beatrice).
We can regard this Prolog program as a first-order theory. The first clause
corresponds to the logical axiom
(x)(y)(z) parent(x, y) parent(y, z) grandparent(x, z)
If we pose the query
?- cousin(henry,beatrice).
then Prolog answers yes, for the query has an obvious proof from the axioms.
If we ask
?- cousin(elizabeth,asterix).
then Prolog answers no, for the query has no proof. But it has not been
disproved, for it is true in some models, false in others. The answer no conveys
less information than yes.
Now imagine we pose the negative query
?- not cousin(elizabeth,asterix).
A typical Prolog system will answer yes because there is no proof of
cousin(elizabeth,asterix).
This treatment of negation is called Negation as Failure; it differs from logical
negation, since the query is not a logical consequence of the axioms. Some people
think that Negation as Failure is a cheap hack, and that researchers should aim
to implement logical negation. Logical negation would answer no to our query —
but this is often undesirable. Prolog programmers recognise that our database
defines relations such as ‘cousin-of’, where
cousin(elizabeth,asterix) does not
hold. Negation as Failure is a natural way to test whether a relation holds.
This point of view is hard to justify under the orthodox paradigm for logic
programming.
Logic Programming and Inductive Definitions 7
3.2 Logic programs as inductive definitions
Here is an alternative paradigm for logic programming. A set of clauses is not
a first-order theory, but the definition of a new logic. The meaning of a logic
program is the set of theorems in this ‘private’ logic: all derivable ground atoms.
This is a monotone inductive definition of a family of sets, one for each predicate.
Inductive definitions appear in all branches of mathematics. The natural
numbers are the least set containing 0 and closed under successor. The Boolean
expressions are the least set containing propositional letters and closed under
, , ¬. Most importantly: the set of theorems in a logical system is the least
set containing all axioms and closed under all applications of inference rules. As
Aczel explains, this is the general form of an inductive definition [2].
What has this to do with logic programming? We can regard a logic program
as an inductive definition by taking its clauses as axioms and inference rules. We
regard our family relationships database as a new logic with rules like
parent(x, y) parent(y, z)
grandparent(x, z)
grandparent(z, x) grandparent(z, y)
cousin(x, y)
This inductively defines various sets. The ‘grandparent of relation is the set of
all pairs hx, yi such that grandparent(x, y) follows from the database. Similarly
the derivable instances of cousin(x, y) define the ‘cousin of relation.
Aczel [2] gives the semantics of an inductive definition as follows.
A rule has the form p P , where P is the set of premises and p is the
conclusion.
Let Φ be a set of rules. A set A is Φ-closed provided that for each rule p P
in Φ,ifP A then p A. (Thus if the premises are in A then so is the
conclusion.)
The set I(Φ) inductively defined by Φ is given by
I(Φ)=
\
{A | A is Φ-closed}
The inductively defined set I(Φ) can also be expressed as the least fixed point
of a monotone operator. A set of rules Φ defines a universe or assertion language
A, namely the set of all premises and conclusions of rules:
A =
[
{P ∪{p}|rule p P is in Φ}
Note that A corresponds to the Herbrand base of a set of clauses. Now Φ defines
a monotone operator φ over A, corresponding to all possible rule applications in
a set Y of assertions. Precisely, if Y A then
φ(Y )={p A | rule p P is in Φ and P Y }
8 Lawrence C. Paulson and Andrew W. Smith
The iterates of φ are defined as usual:
φ
0
=
φ
n+1
= φ
n
φ(φ
n
)
φ
ω
=
[
nω
φ
n
If all rules in Φ have a finite number of premises then φ
ω
= I(Φ) and φ
ω
is the
least fixed point of φ.
The full theory of inductive definitions is complicated, but much of it need
not concern us. A rule p P could have an infinite number of premises, unlike
rules in logic programs. The rules in an inductive definition contain no variables.
A schematic rule (like the rule for cousin) abbreviates an infinite set of rules:
all ground instances under the Herbrand universe.
In the semantics of logic programming, such theory has long been used as a
technical device. We suggest, rather, that an inductive definition is a logic pro-
gram’s intrinsic declarative content. Clauses should not be viewed as assertions
in first-order logic, but as rules generating a set.
For a concrete example, consider how a formal grammar generates the strings
of a language. Grammars are inductive definitions. This may explain why logic
programming works so well at natural language processing.
3.3 Least models and the Closed World Assumption
Definite clauses are a fragment of first-order logic enjoying remarkable qualities.
Any set of definite clauses is consistent. In the greatest model, each predicate
is universally true; in the unique least model, each predicate holds just when it
must. The least model is the interesting one, for it corresponds to our intuition
that our logic program defines a set of relationships.
Van Emden and Kowalski [43] observed that the model-theoretic semantics
of a logic program is best given by the least Herbrand model, which is the
intersection of all Herbrand models. This coincides with the operational and
fixed point semantics. Their fixed point semantics is precisely our inductively
defined set I(Φ).
The least model can be formalized in first-order logic as the Closed World
Assumption, augmenting the database with the negations of all ground atoms
that do not hold in the least model. Shepherdson shows how this leads to diffi-
culties [40]. First-order logic is simply too weak to characterize the least model.
4
Horn clause logic is even weaker. But the least model can be directly expressed
by an inductive definition.
4 By the Skolem-L¨owenheim Theorem, no set of first-order axioms can even fix the
cardinality of its models, let alone fix a single model.
Logic Programming and Inductive Definitions 9
Negation as Failure is investigated by Apt and van Emden [3] and Lloyd [29].
Essentially, they develop the theory of inductive definitions so as to distinguish
divergent computations from finite failures. Negation goes beyond monotone in-
ductive definitions: with negated subgoals, the function φ above may not be
monotone. However, perhaps the database can be partitioned into several in-
ductive definitions, so that each negation refers to a set that has already been
defined (the dependency graph must be acyclic). The database can then be
interpreted as an iterated inductive definition (via some treatment of finite fail-
ure.) Such databases are called stratified or free from recursive negation [44].
The main stream of (sound) research into negation [34] uses the mathematics of
fixedpoints, ordinals, and inductive definitions, not that of classical first-order
logic.
In different situations, either view of logic programming inductive def-
initions or first-order logic could be more useful. Where the Closed World
Assumption is wrong, so is the inductive view. Below we contrast these views
with respect to several aspects of logic programming.
3.4 Specification and verification of logic programs
A key selling point for ‘programming in logic’ is that programs can be viewed as
specifications. Programs can be derived from specifications, and these programs
are guaranteed to be correct. Can we verify logic programs when regarding them
as inductive definitions?
Given a specification, a logic program is correct if it is sound and complete.
Sound (or partially correct) means that each successful goal in an execution is
permitted by the specification. Complete means that each permitted goal will
succeed during execution. Specifications are still written in some sort of logical
formalism even when we regard programs as inductive definitions.
Every inductive definition gives rise to a principle of inductive proof. In simple
cases, this principle resembles structural induction or mathematical induction
(on the natural numbers). The general principle is induction over derivations
in our ‘private’ logic. It is used to prove soundness of logic programs, basically
by showing that each rule is individually sound. Completeness proofs typically
involve some form of induction over the data, showing that the rules suffice for
all the necessary derivations.
Fitting gives examples of correctness proofs [17, pages 49–53]. His book is a
unique treatment of computability theory in the context of logic programming.
He presents logic programs not as first-order theories, but as ‘elementary formal
systems’, which are a restricted case of inductive definitions.
Kowalski [28, page 19] says that a program is totally correct provided it is
logically equivalent to its specification. That is nice and simple. But Hogger
notes [26, page 141]
10 Lawrence C. Paulson and Andrew W. Smith
In practice, though, this is rarely possible: logic procedure sets usually have
less information content than the specifications to which they conform, even
though they may be complete.
Hogger goes on to demonstrate a more general method called definiens transfor-
mation. Each predicate is defined as logically equivalent to some formula. These
definitions are transformed by replacing formulae by equivalent formulae. Fi-
nally each ‘if-and-only-if is replaced by ‘if when this results in a set of definite
clauses. Hogger states [26, page 153]
If this is accomplished successfully then the program (P,G) is thereby proven
to be totally correct. Its partial correctness is directly established .... Its com-
pleteness is established by the fact that each definiens transformation preserves
equivalence ...
Let us illustrate this method by deriving a predicate nat(X) meaning X is
a natural number’. Informally, let us say that X is a natural number if and only
if X has the form s(s(···s(0) ···)).
The specification is
nat(X) X is a natural number.
Now X is a natural number if and only if X =0orX is the successor s(Y )of
some natural number Y . Therefore we may transform the specification to
nat(X) X =0 (Y )(X = s(Y ) Y is a natural number).
Substituting back the original specification introduces a recursive call:
nat(X) X =0 (Y )(X = s(Y ) nat(Y ))
Each disjunct has the form of a clause body, so replace by :
nat(X) X =0 (Y )(X = s(Y ) nat(Y ))
Simplification yields two clauses, a correct program:
nat(0) nat(s(Y )) nat(Y )
But here is another derivation. Note that X is a natural number if and only
if s(X) is a natural numer. Therefore the specification is equivalent to
nat(X) s(X) is a natural number.
Substituting back the original specification introduces a recursive call:
nat(X) nat(s(X))
Logic Programming and Inductive Definitions 11
Dropping the gives the clause
nat(X) nat(s(X)).
How did we get such a useless program? Hogger’s verification method re-
quires a separate termination proof, which must be performed with respect to
a given computational strategy [26, page 143–150]. So the connection between
‘logic program’ and ‘logic specification’ is not as simple as commonly thought.
When the above programs are viewed as inductive definitions, it is obvious
that the first defines the natural numbers and the second defines the empty set.
We can understand the ‘definiens transformations’ as equational reasoning on
sets. If a set satisfies a recursive equation like S = f(S), then S is some fixedpoint
of f . Replacing by picks out the least fixedpoint. These fixedpoints could
differ, as they did above. The theory of inductive definitions could lead to better
techniques [16].
4 Extended logic programming languages
Many extended logic programming languages aim to increase the power of pure
declarative programming. Most extensions adopt the first-order logic viewpoint,
but several are best understood from the viewpoint of inductive definition. While
surveying other work, this section also discusses the design of our logic language
with functions.
4.1 Larger fragments of first-order logic
If our goal is to program in logic then we should go beyond Horn clauses, aim-
ing ultimately at programming in full first-order logic. Bowen [10] proposed
a complete theorem-prover where programs consist of sequents of the form
A
1
,...,A
m
` B
1
,...,B
n
; a standard Prolog interpreter handles the case where
these resemble definite clauses. Many similar proposals have appeared since.
Stickel’s Prolog Technology Theorem Prover [41] exploits the sophistication
of current Prolog implementations. He extends them to full first-order logic
using sound unification with occurs check, the model-elimination inference rule,
and depth-first iterative deepening for completeness. Stickel’s stated aim is high-
performance theorem proving; he specifically de-emphasises its potential for logic
programming [41, page 375].
Applications of a full first-order logic programming language are hard to vi-
sualize. Perhaps the problem is that first-order logic destroys that vital property,
the least model property. The disjunction pq has two minimal models, where ei-
ther p or q is true. Therefore, disjunctive axioms destroy the least model property.
So do negative goals and nested implication, for p ←¬q and q (q p) are
12 Lawrence C. Paulson and Andrew W. Smith
classically equivalent to pq. Makowsky has formalized the least model property
in terms of initial structures and generic examples. He shows that Horn clauses
are the largest fragment of first-order logic enjoying this property [30]. By re-
garding logic programs as inductive definitions, we come to the same conclusion
at once. Makowsky’s work is rigorous confirmation of our intuitive idea.
4.2 Other work concerning inductive definitions
Hagiya and Sakurai [23] present a formal system for logic programming, based
on the theory of iterative inductive definitions. This system captures the least
fixedpoint semantics of a set of clauses and formally justifies Negation as Failure.
They envisage programs consisting of several levels, each defined inductively in
terms of its predecessor. The formal system is given as a foundation for Prolog,
with applications to program specification, verification, and synthesis. Hagiya
and Sakurai take the traditional first-order view of logic programming, but at
times appear to question this paradigm [23, page 71]:
Prolog usually explained as being based on SLD-resolution. It is more nat-
ural, however, to regard a Prolog program and its execution as a set of
productions and generation of a normal proof than to regard them as a set of
Horn clauses and SLD-resolution, since it more faithfully reflects the procedu-
ral interpretation of predicate logic ...Some resolution procedures are more
clearly understood in terms of deduction, even if deduction and refutation are
equivalent.
Halln¨as and Schroeder-Heister [25] advance a view of logic programming
based on inductive definitions. Calling the traditional view ‘clauses-as-formulae’,
they advocate instead ‘clauses-as-rules’: the clauses are a system of inference
rules. Their approach is inspired by natural deduction proof theory and defines
the semantics of programs as inductively defined sets. They model non-ground
answer substitutions directly, not as the set of ground instances.
Their language of Generalized Horn Clauses resembles earlier proposals for
permitting nested implications in clause bodies [20, 33], but they obtain a much
simpler treatment of free variables by distinguishing assumptions from program
clauses. Nested implication falls outside the framework of monotone inductive
definitions (as remarked above) but programs can be understood as partial in-
ductive definitions [24].
Their approach includes a new idea, similar to elimination rules in natural
deduction. A predicate p is inductively defined by its set of introduction rules,
namely the clauses with head p. If we then are told that p happens to be true,
then the body of some introduction rule must also be true. This gives a form of
Negation as Failure and non-monotonic reasoning. Aronsson et al. [4] describe
the language in more detail and discuss a prototype implementation.
Logic Programming and Inductive Definitions 13
4.3 The language Loglisp
Loglisp, by Robinson and Sibert, is one of the earlist attempts to combine logic
and functional programming [39, page 400]:
Our own early attempts (as devoted users of Lisp)touseProlog convinced
us that it would be worth the effort to create within Lisp a faithful imple-
mentation of Kowalski’s logic programming idea. ... We set out to honor the
principle of the separation of logic from control (no CUT, no preferred order-
ing of assertions within procedures nor of atomic sentences within hypotheses
of assertions) by making the logic programming engine ‘purely denotative’.
Loglisp is not completely pure. The Logic component can invoke arbitrary
Lisp functions. A more fundamental problem is the treatment of uninstantiated
variables in function calls. Loglisp leaves such variables unchanged during ex-
pression reduction, so the result can depend on the order in which goals are
solved. Dincbas and van Hentenryck show how this leads to anomalies [15].
But Loglisp’s Horn clause interpreter — thanks to a form of best-first search
is complete. Although Prolog’s depth-first search strategy is incomplete, this
only matters if the search space is infinite, when we must be prepared to give
up after a finite time. So a call to the Logic interpreter specifies how many
solutions to find before stopping. We could say that Loglisp views the clauses
as an inductive definition of solution sets; the Lisp half operates on lists of
solutions from the Logic half. This resembles Prolog’s setof predicate.
Robinson’s later work [38] aims to integrate the functional and logic compo-
nents using a single reduction semantics for both.
4.4 A logic language with functions
Though many combined languages have appeared since Loglisp, few tackle
the problem of uninstantiated variables. Equational unification, although not
completely understood, seems to be the solution. Equational unification treats
functions in a natural way, retaining Prolog’s bidirectionality: functions can
be inverted. We have implemented such a language; a similar one is Ideal [9].
An inductive view of functions in clauses Viewing logic programs as in-
ductive definitions gives a framework for a logic language with functions. Recall
that the inference rules in an inductive definition contain no variables. A rule
containing variables is merely an abbreviation for the set of its ground instances.
We extend this means of abbreviation by permitting clauses to invoke functions
defined in a functional language. Such a rule abbreviates the set of its ground
instances where all functions have been evaluated.
14 Lawrence C. Paulson and Andrew W. Smith
Terms may contain constructors and defined functions. Constructors, such
as constants, the pairing operator, and list Cons, generate what amounts to a
Herbrand universe. Functions denote operations over this universe. Computable
values are elements of this universe. Solution terms need not be ground, but
should contain no defined functions. (Henceforth we shall just say ‘function’,
not ‘defined function’.)
For example, suppose f and g are functions. The clause
p(f(X),Y) p(X, g(Y ))
stands for the set of instances
p(u, y) p(x, v)
where u, v, x, and y are values such that u is the value of f(x) and v is the value
of g(y), provided the function calls terminate.
In the first-order logic view of logic programming, programs in this kind
of language are viewed as theories in Horn Clause logic plus equality. Severe
restrictions are imposed on equality so that programs can be executed. The
equalities must form a term rewriting system with strong properties: they must
be confluent, left-linear, and terminating. In short, the equalities must take the
form of function definitions. The model theory of Horn Clauses with equality, as
developed by Goguen and Meseguer [22], conveys no clear picture of what their
programs compute.
An inductive definition has an intuitive reading as a process generating a set
of results using some rules. The function definitions help to generate the set of
ground rules. An alternative picture: function evaluation is interleaved with rule
application. An implementation must ‘guess’ suitable instances using some sort
of unification. Resolving the goal p(a, g(b)) against p(f(X),Y), requires solving
the equations a = f(X) and g(b)=Y by unification in the equational theory of
the functions f and g.
The unification process can be seen as symbolic evaluation of the defined
functions. Suppose that the list append is defined as follows:
app([],V) V
app([X | U ],V) [X | app(U, V )]
The goal app(U, V )=[a, b] calls the function app with uninstantiated arguments.
Solving the goal requires unifying [a, b] with app(U, V ). The list [a, b] is already
a construction; but app(U, V ) must be rewritten, and this instantiates U .
U = [], the first rewrite reduces app(U, V )toV , giving the solution V =[a, b].
U =[X
1
| U
1
], for new variables X
1
and U
1
, reduces app(U, V )to[X
1
|
app(U
1
,V)]. Unification sets X
1
= a and leaves the problem of unifying [b]
with app(U
1
,V). There are two subcases.
Logic Programming and Inductive Definitions 15
U
1
= [] gives the solution U =[a] and V =[b].
U
1
=[X
2
| U
2
] sets X
2
= b, where we must unify [] with app(U
2
,V).
U
2
= [] gives the solution U =[a, b] and V = [].
U
2
=[X
3
| U
3
] terminates the search because [] and [X
3
| app(U
3
,V)]
cannot be unified.
This is essentially narrowing, a special case of the paramodulation rule used
in unification algorithms for suitable equational theories [46]. There are several
implementations [19, 27] and many variations. We have chosen a form of lazy
narrowing where a function’s arguments are evaluated only when necessary
in the hope of postponing the discovery that a variable is uninstantiated.
Fribourg has dealt with the strict case [18].
Treatments of negation and ‘cut’ The cut, written (!), curtails backtracking.
Cuts can speed the search exponentially by pruning redundant parts of the
search space. Cuts also compensate for implementation deficiencies, preventing
the ‘trail’ of choice points from using up too much store. Cuts are by far the
commonest impurity, and are often used needlessly. How can we do without
them?
Negation as Failure can be defined through cut [13]:
not(P) :- call(P), !, fail.
not(P).
Conversely, Clocksin and Mellish recommend replacing cuts by negations when-
ever possible. So a pure language must include a clean treatment of Negation as
Failure. This line of research is orthogonal to our own; see Minker [34].
Cuts are often used when expressing functional dependence, forcing the non-
deterministic Prolog machine to behave deterministically. By having functions
in our language, we reduce the need for cuts; indeed, our prototype interpreter
dynamically inserts cuts when evaluating functions.
Predicates should not be confused with boolean-valued functions. Although
predicates can be represented by their characteristic functions, few logical sys-
tems formally identify them.
5
The booleans true and false are symmetric while
success and failure are not. Whether a boolean function returns true or false,
it has terminated successfully. But failure conveys less information than success,
and may not happen in finite time.
Simple tests, such as arithmetic comparisons, should be boolean functions
rather than predicates. Testing whether a boolean expression equals false gives
5 The main formal system that does is classical higher-order logic. Intuitionistic higher-
order logic identifies predicates with propositional functions, but the corresponding
set of truth values is not a Boolean algebra.
16 Lawrence C. Paulson and Andrew W. Smith
a kind of negation. Our language does not allow conditional equations. The
conditional rewrite rule
a = b if p
where the condition p is a predicate, says nothing about a when p does not hold.
Instead we prefer a conditional expression controlled by a boolean expression c:
a = if c then b
1
else b
2
5 A Prototype of the language
This implementation was developed (by A. W. Smith) to investigate what was
possible and what was desirable in such a language. It was written as an inter-
preter in Prolog to carry over such features as the parser and backtracking
mechanism. We have not investigated how low-level techniques for logic and
functional languages might be integrated.
A program file contains clauses, function rewrite rules, type declarations,
operator declarations and comments. The system is similar to a simple Prolog
interpreter but uses a lazy semantic unification algorithm. The syntax of definite
clauses follows that of Edinburgh Prolog. Built in predicates include true,
which always succeeds; fail, which always fails; and A = B, which succeeds if
A and B unify semantically. Prolog’s (extralogical!) output predicates nl and
write are also provided.
6
5.1 Rewrite Rules
Function definitions are given as a series of rewrite rules of the form
f (expr
1
, expr
2
,...) --> expr .
For example, the append function for list is
app([], V) ->> V.
app([X|U], V) ->> [X | app(U,V)].
Answers to queries are returned as in Prolog. When a solution is found,
the system returns bindings for the variables. Further answers may be elicited
using the ‘;’ key. Example:
6 Because of iterative deepening, the search may pass through the same point several
times and will repeat the output on each occasion.
Logic Programming and Inductive Definitions 17
| ?- solve(app(U,V)=[1,2]).
U=[]
V=[1,2] ;
U=[1]
V=[2] ;
U=[1,2]
V=[] ;
no
To ensure that the rewrite rules form a confluent system, certain restrictions
are imposed. Though these are not necessary conditions, they are sufficient and
are, furthermore, not unreasonable for a functional language:
The Constructor Discipline. The names are divided into two disjoint classes
functions and constructors depending on whether there are rewrite
rules for that name. No function should appear in the arguments of the left
side of any rule.
Left Linearity. No variable should appear more than once in the left side of a
rule.
Term Rewriting. All the variables appearing in the right side of a rule should
also appear in the left side of the rule.
Non-overlapping. No two rule left sides should be unifiable with each other.
(The rules should be mutually exclusive.)
If any condition is broken, the offending rewrite rule is identified in a warning.
The system does not prevent the user defining and using non-confluent rules, but
it contains optimisations based on the assumption that the conditions are obeyed.
The interpreter assumes the constructor discipline holds, so defined functions
within the left side of a rule are treated like constructors. Left linearity enables
the interpreter to omit the occurs check when it unifies the function with the
left side of a rule in the application of that rule. Rules which are not left linear
may cause the system to loop or crash.
Finally, the assumption that rules do not overlap allows the interpreter to
reduce the search space. If a function application is rewritten without instantiat-
ing any variables, then no other rule can be applicable unless it overlaps with the
first. The effect is like cut upon backtracking, no other rules need be tried.
7
The system also checks whether the rewrite rules exhaust all possible values
for the arguments. This check is rather involved. The approach is to consider the
tuples formed by the arguments of the left sides of the rules. A tuple of variables
7 A clever user could make use of this final optimisation to provide a default case for
a function. This would work only for ground arguments.
18 Lawrence C. Paulson and Andrew W. Smith
is then successively instantiated (to terms of the appropriate type) so that it
does not unify with each of the rule tuples taken in turn.
5.2 Type Declarations
The built-in types include int and bool. Type operators such as list can be
declared, permitting types such as list(int) and list(list(bool)). Poly-
morphic types like list(A) are permitted, as well as function types with the
following syntax:
[type
1
, type
2
,...] =>> type
Type declarations must be given for all predicates, constructors and func-
tions. Predicate type declarations take the form
pred p(type
1
, type
2
,...) .
where type
1
, type
2
,... are the types of the arguments. The type may be poly-
morphic: a general list appending predicate could be declared by
pred append(list(A), list(A), list(A)).
The types of constructors are declared by
constructors type => con
1
, con
2
,....
Here con
1
, con
2
,... are made up from a constructor for the given type, applied
to type arguments. For example, the constructors for the natural numbers (type
nat) are zero and s, where s must be applied to an argument of type nat.
constructors nat => zero, s(nat).
The constructors for the polymorphic type list(A) are given as follows (we can
use standard Prolog list syntax, denoting Prolog’s list constructors):
constructors list(A) => [], [A|list(A)].
The type of a function is declared as follows:
function f (type
1
, type
2
,...) =>> type.
For example:
function mult(nat,nat) =>> nat.
function app(list(A),list(A)) =>> list(A).
The type checking scheme follows Mycroft and O’Keefe [35] with a straight-
forward extension to include function rewrite rules.
A typeless version of the language could be envisaged. Type information is
required by the built in equality function.
Logic Programming and Inductive Definitions 19
5.3 The equality function ‘eq’
The equality function eq returns true or false. It behaves something like a
set of rewrite rules, testing all possible combinations of constructors for a given
type. The equality test is defined for each (non-function) type.
For example, the equality test for lists behaves something like the following
function:
[] listeq [] ->> true.
[] listeq [A|As] ->> false.
[A|As] listeq [] ->> false.
[A|As] listeq [B|Bs] ->> (A eq B) and (As listeq Bs).
Here and denotes Boolean conjunction, while eq is an equality test for the list’s
element type.
The test is more efficient than suggested above, for Xeqtgives true while
instantiating X to t. When returning false, the equality test instantiates its
arguments to all possible pairs of different constructors.
5.4 Other features
Arithmetic To perform integer arithmetic there are built in integer valued
functions +, -, *, div, mod, and abs with the obvious meanings. These use the
arithmetic routines of the host language but behave as if they are defined by
an infinite collection of rewrite rules such as 0+0->>0, 0+1->>1, -1+0->>-1, etc.
For example, the goal 15=X+Y succeeds infinitely often, finding all pairs of num-
bers that sum to 15. This may seem odd, but the implementation aims to be
complete. The Prolog goal 15 is X+Y typically results in an error message.
The relations >, <, >=, and =< are boolean valued functions — not predicates
since they are decidable.
Integers have type int; booleans have type bool.
Higher-order functions The built in function apply returns the value ob-
tained by applying the function contained in its first argument to the list of
arguments contained in its second argument. Thus apply(+,[1,2]) returns 3.
Since there is no higher-order unification, when apply is rewritten, the first
argument must be normalisable to a function or a λ-expression.
The user can define higher-order functions:
map(F, []) ->> []
map(F, [H|T]) ->> [apply(F,[H]) | map(F,T)]
20 Lawrence C. Paulson and Andrew W. Smith
The value of map(lambda([X],X * X),[1,2,3]) is [1,4,9].
The justification of higher-order functions requires extending the theory of
narrowing to allow function variables in rewrite rules. This appears straight-
forward if function variables are not allowed in goals. The full incorporation
of function variables would require higher-order equational unification. Our ex-
perience with higher-order unification shows that it can be effective in simple
cases [36], but it also suggests that ambitious applications are impractical.
Descriptions Descriptions, or η-terms, call the predicate level from the func-
tion level. The term eta(X,G) means ‘some X such that G’, and returns some
value of variable X that makes goal G succeed. Remaining values are found on
backtracking. Ideal has a similar feature [9, page 90].
Descriptions cause expressions to be nondeterministic, violating the separa-
tion of concerns into functions (deterministic) and predicates (nondeterministic).
They pose interesting but very difficult semantic questions.
6 Operation of the prototype
The interpreter is essentially Prolog with a modified unification algorithm to
allow defined functions within terms. The unification algorithm is similar to that
given by Martelli, Moiso and Rossi [31]. It effectively uses a selection strategy
for narrowing described as ‘outer narrowing’ by You [45, 46]. You describes a
matching algorithm; we have extended this to a unification algorithm but have
not attempted a proof of correctness. The occurs check during unification could
perhaps be omitted by allowing cyclic expressions to denote fixedpoints.
6.1 Unification
The effect of the unification algorithm can be described as follows. In unifying
two terms A and B
1. If either term is a variable, then instantiate that variable if permitted by
occurs check (see below). Unification is lazy: the other term may contain
function terms which are not rewritten now (maybe never).
2. If both are constructor terms, then if they have different principal construc-
tors the unification fails, otherwise the two sets of arguments are unified.
3. Otherwise one term must be a function application. It is rewritten by nar-
rowing (see later) to some new term, which must unify with the other term.
Only (3) distinguishes this from ordinary unification.
Logic Programming and Inductive Definitions 21
Rewriting A function application is rewritten using a rewrite rule from the
function’s definition. In a functional language this is achieved by matching the
term with the left side of the rule. In a logic language, however, there may well
be logic variables within the term; some instance of these variables might be
needed to apply the rule. A narrowing step consists of unifying a term with the
left side of a rule and replacing it with the right side.
If there are nested function applications, which should be chosen for narrow-
ing? The brute-force approach would be to try every possible occurrence. This
will certainly find all possible solutions but could produce the same solution
many times. A practical selection strategy would chose a single occurrence. The
obvious choices are the innermost or outermost occurrence.
The innermost strategy can be shown to be complete provided the rewrite sys-
tem is confluent and terminating and the functions are exhaustively defined [18].
However, an innermost strategy is eager and one of the design objectives of the
language was that it should be lazy.
The outermost strategy is incomplete (see also You [46]):
f(W, a) af(a, b) b
Given the term f(f (X, Y ),Z), the innermost strategy can produce both a and
b, while the outermost strategy only produces a. The problem arises because
f(X, Y ) can only be unified syntactically with the variable W in the first rule.
However if it were rewritten first to a the second rule could be used as well
in other words a and f(X, Y ) unify semantically when applying the second rule.
The solution is to use an outermost selection strategy (allowing laziness), but
when narrowing, the unification between the term and rewrite rule should be se-
mantic rather than syntactic. This differs from the usual definition of narrowing.
An advantage of using Prolog as the host language is that the logic variables
can be used as pointers. When a variable is instantiated, that value is propagated
with no need to make substitutions explicitly. However the value of the pointer
cannot be reassigned when a function application is rewritten. The same term
could be evaluated repeatedly, giving call-by-name. To get call-by-need, function
applications are represented by a structure containing a new variable to point
to the rewritten value.
The unification algorithm is lazy and so only performs rewriting when re-
quired. However, the user requires answers in normal form. Thus after the sys-
tem has found a solution it normalises the answer substitutions by repeatedly
rewriting any defined function terms in the substitutions. The same rewriting
algorithm is used as in unification. (This normalisation is too eager; in conse-
quence, infinite data structures cannot be displayed as results, although they
may take part in computations.)
22 Lawrence C. Paulson and Andrew W. Smith
The occurs check When a variable is to be unified with a term, it must be
checked not to occur within the term. In the presence of rewrite rules an oc-
currence within a defined function might disappear: thus X and 0 + X unify
semantically.
The occurs check does not fail just because there is a syntactic occurrence
of the variable within the term. Instead, it copies the term, replacing by a new
variable each function application containing an occurrence, adding these as
new disagreement pairs to (eventually) solve. If an occurrence of the variable is
encountered other than in a function application, then the check fails. The new
term built in this way will not contain any occurrences of the variable.
Without this extended occurs check, cases involving occurrence would have
to be solved using the full unification algorithm, which would be slower.
Search strategy The search for a solution uses depth-first iterative deepen-
ing [41]. During each iteration the search is cut off if it exceeds the given limit.
At the end of the iteration, if the search has been cut off at any point, then the
limit is increased and the next iteration started. When a solution is found, its
depth is checked to be within the range of the current iteration to prevent a
solution being returned several times.
The depth is incremented when either rewrites or clauses are applied. Thus
the search is complete at both the function and clause level.
Iterative deepening is complete, straightforward to implement, and gives a
sensible compromise between time and space efficiency. Perhaps some more so-
phisticated strategy would be more efficient, while retaining completeness.
6.2 Example: An Eight Queens Program
This eight queens program begins by introducing the type list. Predicate upto
generates a range of numbers, while queens generates boards of non-attacking
queens. The membership test is a function (mem) since its negation is used.
Predicate safe could also be a boolean function.
constructors list(A) => [],[A|list(A)].
pred queens(int,list(int)).
queens(N,[Q|B]) :- (N>0)=true, queens(N-1,B), upto(1,8,Q),
mem(Q,B)=false, safe(1,Q,B).
queens(0, []).
pred upto(int,int,int).
upto(M,N,M) :- (M=<N)=true.
upto(M,N,K) :- (M<N)=true, upto(M+1,N,K).
Logic Programming and Inductive Definitions 23
function mem(A,list(A)) =>> bool.
mem(X,[]) ->> false.
mem(X,[Y|Ys]) ->> (X eq Y) or mem(X,Ys).
pred safe(int,int,list(int)).
safe(_,_,[]).
safe(I,Q,[Q1|B]) :- (I eq abs(Q-Q1))=false, safe(I+1,Q,B).
Our interpreter is slow, taking six minutes to find the first three solutions.
| ?- solve(queens(8,B)).
B=[4,2,7,3,6,8,5,1] ;
B=[5,2,4,7,3,8,6,1] ;
B=[3,5,2,8,6,4,7,1]
yes
6.3 Example: A Propositional calculus theorem prover
Wang’s Algorithm for the propositional calculus [32] works by constructing a
backwards proof using the rules of the sequent calculus [21]. The following pro-
gram also constructs a proof tree. Each label names some sequent calculus rule,
such as andl for -left. The subtrees represent proofs of the premises of the rule.
This program demonstrates function inversion. The function sizeof com-
putes the size of proof trees. In Prolog this function must be coded by two
different predicates, depending on whether it is used to compute the size or
(backwards) to generate trees of a given size.
The program begins with Prolog infix declarations. Types of formulae,
labels, and trees are declared. Note that a tree node contains a label and a list
of subtrees. The remainder of the program is basically Prolog. The program
appears in Figure 1.
The first sample execution demonstrates programming with non-ground data.
Here we construct non-ground trees of size 5, then instantiate them to valid proof
trees. This generates theorems whose proofs have size 5. A similar program in
Prolog crashed due to a cycle (no occurs check).
The output has been beautified by indenting and by shortening internal vari-
able names.
24 Lawrence C. Paulson and Andrew W. Smith
Fig. 1. A Program for Wang’s Algorithm
/*logical connectives are constructors of type form. */
op(5,fy,~). op(10,xfy,&). op(20,xfy,\/). op(30,xfy,-->).
constructors form => &(form,form), \/(form,form), ~(form),
-->(form,form), p,q,r.
/*labels of proof trees*/
constructors label => basic(form),andl,andr,orl,orr,notl,notr,
impl,impr,iffl,iffr.
constructors tree => node(label, list(tree)).
function sizeof(tree) =>> int.
function sizeoflist(list(tree)) =>> int.
sizeof(node(L,Ts)) ->> sizeoflist(Ts) + 1.
sizeoflist([]) ->> 0.
sizeoflist([T|Ts]) ->> sizeof(T) + sizeoflist(Ts).
/*delmem(X,Ys,Zs) finds and removes X from Ys giving Zs */
pred delmem(A,list(A),list(A)).
delmem(X,[X|Xs],Xs).
delmem(X,[Y|Ys],[Y|Zs]) :- delmem(X,Ys,Zs).
/*common(As,Bs,B) when B is a common element of As and Bs*/
pred common(list(A),list(A),A).
common(As,[B|_],B) :- delmem(B,As,_).
common(As,[_|Bs],B) :- common(As,Bs,B).
/*proof(left formulae,right formulae,proof tree) */
pred proof(list(form), list(form), tree).
proof(As,Bs,node(basic(B),[])) :- common(As,Bs,B). /*0 subproofs*/
/*1 subproof*/
proof(As,Bs,node(notr,[T])) :- delmem(~B,Bs,Ds),proof([B|As],Ds,T).
proof(As,Bs,node(andl,[T])) :- delmem(A1&A2,As,Cs), proof([A1,A2|Cs],Bs,T).
proof(As,Bs,node(orr,[T])) :- delmem(B1\/B2,Bs,Ds),proof(As,[B1,B2|Ds],T).
proof(As,Bs,node(notl,[T])) :- delmem(~A,As,Cs), proof(Cs,[A|Bs],T).
proof(As,Bs,node(impr,[T])) :- delmem(B1-->B2,Bs,Ds),
proof([B1|As],[B2|Ds],T).
/*2 subproofs*/
proof(As,Bs,node(andr,[T1,T2])) :- delmem(B1&B2,Bs,Ds),
proof(As,[B1|Ds],T1), proof(As,[B2|Ds],T2).
proof(As,Bs,node(orl,[T1,T2])) :- delmem(A1\/A2,As,Cs),
proof([A1|Cs],Bs,T1), proof([A2|Cs],Bs,T2).
proof(As,Bs,node(impl,[T1,T2])) :- delmem(A1-->A2,As,Cs),
proof(Cs,[A1|Bs],T1), proof([A2|Cs],Bs,T2).
Logic Programming and Inductive Definitions 25
?- solve((5=sizeof(T), proof([],[B],T))).
T=node(andr,[node(impr,[node(basic(_1),[])]),
node(impr,[node(basic(_2),[])])])
B=(_1-->_1)&(_2-->_2) ;
T=node(impr,[node(andr,[node(basic(_1),[]),
node(impr,[node(basic(_2),[])])])])
B=_1-->_1&(_2-->_2) ;
T=node(impr,[node(andr,[node(basic(_1),[]),
node(impr,[node(basic(_1),[])])])])
B=_1-->_1&(_2-->_1) ;
T=node(impr,[node(andr,[node(basic(_1&_2),[]),
node(andl,[node(basic(_1),[])])])])
B=_1&_2-->(_1&_2)&_1
yes
The program can also prove theorems and report the size of the proof.
?- solve((proof([],[p & (q & r) --> (p & q) & r],T), N=sizeof(T))).
T=node(impr,[node(andl,[node(andl,
[node(andr,[node(andr,[node(basic(p),[]),
node(basic(q),[])]),
node(basic(r),[])])])])])
N=8
yes
7 Conclusions
We have criticised logic programming as a declarative paradigm. What can be
done to make pure logic programming practical?
To justify the Closed World Assumption, we propose that logic programs
should be viewed as inductive definitions, not as first-order theories. Some people
refuse to abandon the dream of programming in first-order logic. But we have to
ask whether this dream is possible — even whether it is desirable. The first-order
paradigm does not deal adequately with negation in databases, and seems to be
an unreliable guide in research on program correctness and language design.
Inductive definitions are more fundamental than first-order logic, and perhaps
easier to understand.
Uses of Prolog’s cut can be largely eliminated by providing functions and
negation. Two forms of negation can be mathematically justified: testing whether
a boolean expression returns false, and restricted forms of Negation as Failure.
Concepts such as ‘stratified program’ are best understood from the perspective
26 Lawrence C. Paulson and Andrew W. Smith
of inductive definition. Future languages should provide means for organizing a
program into a suite of inductive definitions, since stratification will not hold by
accident.
The Prolog goal var(X) tests whether or not the variable X is bound. This
clearly has no logical meaning. Some uses of var can be eliminated by allowing
functions to accept non-ground arguments. But there are some difficult examples,
such as writing a polymorphic type checker [11]. This is natural to write in
Prolog, since it uses unification and search. When the type has been inferred,
certain of its variables must be labelled as ‘generic’, and such manipulation of
logical variables must use var.
Such uses of var serve not to make up for language deficiencies, but to ex-
ploit global properties of the program. Similarly, it is hard to eliminate certain
instances of cut. We must either retain such impurities in our languages, or be
prepared to tolerate some inconvenience.
Input/output is the greatest challenge. Pure approaches to input/output con-
stitute much of the research in functional programming, and perhaps could be
applied to the functional part of our language. Most approaches involve contin-
uations, so our extended logic languages must provide higher-order functions.
Our prototype is far too slow for programmers. But the authors of Ideal,
a similar language, claim outstanding efficiency [5, 8]. Their system translates
functions into Prolog clauses, and then into a modified Warren Abstract Ma-
chine. It is incomplete due to depth-first search, but presumably there could be
a version using iterative deepening. An OR-parallel machine such as DelPhi [12]
could support such languages in future. Functions make explicit the granularity
for OR-parallelism: evaluation is deterministic while search is not.
Acknowledgements. This work was supported by the Alvey Diamond project:
SERC grants GR/E/02369 and GR/F/10811. William Clocksin, Martin Hyland,
Tobias Nipkow, Andrew Pitts, Peter Schroeder-Heister, Lincoln Wallen, David
Wolfram, and the referees commented on drafts of this paper.
References
1. Abelson, H., Sussman, G. J., Structure and Interpretation of Computer Programs,
MIT Press, 1985
2. Aczel, P., An introduction to inductive definitions, In Handbook of Mathematical
Logic, J. Barwise, Ed. North-Holland, 1977, pp. 739–782
3. Apt, K. R., van Emden, M. H., Contributions to the theory of logic programming,
J. ACM 29 (1982), 841–862
4. Aronsson, M., Eriksson, L.-H., aredal, A., Halln¨as, L., Olin, P., The program-
ming language GCLA a definitional approach to logic programming, New
Generation Computing 7 (1990), 381–404
Logic Programming and Inductive Definitions 27
5. Bellia, M., Bosco, P. G., Giovannetti, E., Levi, G., Moiso, C., Palamidessi, C., A
two-level approach to logic plus functional programming integration, In PARLE:
Parallel Architectures and Languages Europe, J. W. de Bakker, A. J. Nijman,,
P. C. Treleaven, Eds., vol. I. Springer, 1987, pp. 374–393, LNCS 258
6. Bellia, M., Levi, G., The relation between logic and functional languages: A survey,
J. Logic Prog. 3 (1986), 217–236
7. Bird, R., Wadler, P., Introduction to Functional Programming, Prentice-Hall,
1988
8. Bosco, P. G., Cecchi, C., Moiso, C., IDEAL & K-LEAF implementation: A
progress report, In PARLE: Parallel Architectures and Languages Europe (1989),
E. Odijk, M. Rem,, J.-C. Syre, Eds., vol. I, Springer, pp. 413–432, LNCS 365
9. Bosco, P. G., Giovannetti, E., Ideal: An Ideal DEductive Applicative Language,
In Symposium on Logic Programming (1986), ieee Comp. Soc. Press, pp. 89–94
10. Bowen, K. A., Programming with full first-order logic, In Machine Intelligence
10, J. E. Hayes, D. Michie,, Y.-H. Pao, Eds. Ellis Horwood Ltd, 1982, pp. 421–440
11. Cl´ement, D., Despeyroux, J., Despeyroux, T., Hascoet, L., Kahn, G., Natural
semantics on the computer, Tech. Rep. Research Report 416, INRIA Sophia-
Antipolis, 1985
12. Clocksin, W. F., Principles of the DelPhi parallel inference machine, Comput. J.
30 (1987), 386–392
13. Clocksin, W. F., Mellish, C. S., Programming in Prolog, 3rd ed., Springer, 1987
14. DeGroot, D., Lindstrom, G., Eds., Logic Programming: Functions, Relations and
Equations, Prentice-Hall, 1986
15. Dincbas, M., van Hentenryck, P., Extended unification algorithms for the integra-
tion of functional programming into logic programming, J. Logic Prog. 4 (1987),
199–227
16. Eriksson, L.-H., Halln¨as, L., A programming calculus based on partial inductive
definitions, with an introduction to the theory of partial inductive definitions,
Tech. Rep. R88013, Swedish Institute of Computer Science, 1988
17. Fitting, M., Computability Theory, Semantics, and Logic Programming, Oxford,
1987
18. Fribourg, L., SLOG: A logic programming language interpreter based on clausal
superposition and rewriting, In Symposium on Logic Programming (1985), ieee
Comp. Soc. Press, pp. 172–184
19. Fribourg, L., Prolog with simplification, In Programming of Future Generation
Computers, K. Fuchi, M. Nivat, Eds. Elsevier Science Publishers, 1988, pp. 161–
183
20. Gabbay, D. M., Reyle, U., N-PROLOG: An extension of PROLOG with hypo-
thetical implications. I., J. Logic Prog. 4 (1984), 319–355
21. Gallier, J. H., Logic for Computer Science: Foundations of Automatic Theorem
Proving, Harper & Row, 1986
22. Goguen, J. A., Meseguer, J., Eqlog: Equality, types, and generic modules for
logic programming, In Logic Programming: Functions, Relations and Equations,
D. DeGroot, G. Lindstrom, Eds. Prentice-Hall, 1986, pp. 295–364
28 Lawrence C. Paulson and Andrew W. Smith
23. Hagiya, M., Sakurai, T., Foundation of logic programming based on inductive
definition, New Generation Computing 2 (1984), 59–77
24. Halln¨as, L., Partial inductive definitions, Theoretical Comput. Sci., in press
25. Halln¨as, L., Schroeder-Heister, P., A proof-theoretic approach to logic program-
ming I. Generalized Horn clauses, Tech. Rep. R88005, Swedish Institute of Com-
puter Science, 1988
26. Hogger, C. J., Introduction to Logic Programming, Academic Press, 1984
27. Hussmann, H., Rapid prototyping for algebraic specifications R A P system
user’s manual, Tech. Rep. MIP-8504, Fakult¨at f¨ur Mathematik und Informatik,
Universit¨at Passau, Germany, Mar. 1985
28. Kowalski, R., The relation between logic programming and logic specification,
In Mathematical Logic and Programming Languages, C. A. R. Hoare, J. C. Shep-
herdson, Eds. Prentice-Hall, 1985, pp. 11–27
29. Lloyd, J. W., Foundations of Logic Programming, Springer, 1984
30. Makowsky, J. A., Why Horn formulas matter in computer science: Initial struc-
tures and generic examples, J. Comput. and Syst. Sci. 34 (1987), 266–292
31. Martelli, A., Moiso, C., Rossi, G. F., An algorithm for unification in equational
theories, In Symposium on Logic Programming (1986), ieee Comp. Soc. Press,
pp. 180–186
32. McCarthy, J., Abrahams, P. W., Edwards, D. J., Hart, T. P., Levin, M. I., LISP
1.5 Programmer’s Manual, MIT Press, 1962
33. Miller, D., A theory of modules for logic programming, In Symposium on Logic
Programming (1986), ieee Comp. Soc. Press, pp. 106–114
34. Minker, J., Ed., Foundations of Deductive Databases and Logic Programming,
Morgan Kaufmann, 1988
35. Mycroft, A., O’Keefe, R. A., A polymorphic type system for Prolog, Art. Intel.
23 (1984), 295–307
36. Paulson, L. C., Natural deduction as higher-order resolution, J. Logic Prog. 3
(1986), 237–258
37. Robinson, J. A., A machine-oriented logic based on the resolution principle,
J. ACM 12 (1965), 23–41
38. Robinson, J. A., Beyond loglisp: combining functional and relational program-
ming in a reduction setting, Tech. rep., Syracuse University, 1985
39. Robinson, J. A., Sibert, E. E., LOGLISP: an alternative to PROLOG, In Machine
Intelligence 10, J. E. Hayes, D. Michie,, Y.-H. Pao, Eds. Ellis Horwood Ltd, 1982,
pp. 399–419
40. Shepherson, J. C., Negation in logic programming, In Foundations of Deduc-
tive Databases and Logic Programming, J. Minker, Ed. Morgan Kaufmann, 1988,
pp. 19–88
41. Stickel, M. E., A Prolog technology theorem prover: Implementation by an ex-
tended Prolog compiler, J. Auto. Reas. 4, 4 (1988), 353–380
42. Turner, D. A., Functional programs as executable specifications, In Mathemati-
cal Logic and Programming Languages, C. A. R. Hoare, J. C. Shepherdson, Eds.
Prentice-Hall, 1985, pp. 29–54
Logic Programming and Inductive Definitions 29
43. van Emden, M. H., Kowalski, R. A., The semantics of predicate logic as a pro-
gramming language, J. ACM 23 (1976), 733–742
44. Van Gelder, A., Negation as failure using tight derivations for general logic pro-
grams, In Foundations of Deductive Databases and Logic Programming, J. Minker,
Ed. Morgan Kaufmann, 1988, pp. 149–176
45. You, J.-H., Solving equations in an equational language, In Algebraic and Logic
Programming (1988), J. Grabowski, P. Lescanne,, W. Wechler, Eds., Springer,
pp. 245–254, LNCS 343
46. You, J.-H., Enumerating outer narrowing derivations for constructor-based term
rewriting systems, J. Symb. Comput. 7 (1989), 319–342
This article was processed using the L
a
T
E
X macro package with ICM style
... First-order logic cannot characterize this model, but it can be directly expressed in higher-order logic by an inductive de nition. The view of logic programs as inductive de nitions has been explored (primarily in the context of negation-as-failure) by a number of authors including Hagiya and Sakurai (1984), Aronsson et al. (1991) and Paulson and Smith (1991). Here we adopt this view and use it to establish the correctness of another way of formalizing development schemata. ...
... With respect to the formalization based on inductive definitions, equivalence means provable equivalence in higher-order logic in a theory where the program schemata are formalized as inductively de ned relations. This approach, of viewing pure logic programs not as de ning a rst-order theory, but rather inductive de nitions, and reasoning about these de nitions, has been also taken by Paulson and Smith (1991), Aronsson et al. (1991) and Hagiya and Sakurai (1984). ...
Article
We show how the formalization and application of schemata for program development can be reduced to the formalization and application of derived rules of inference. We formalize and derive schemata as rules in theories that axiomatize program data and programs themselves. We reduce schema-based program development to ordinary theorem proving, where higher-order unification is used to apply rules. Conceptually, our formalization is simple and unifies divergent views of schemata, program synthesis, and program transformation. Practically, our formalization yields a simple methodology for carrying out development using existing logical frameworks; we illustrate this in the domain of logic program synthesis and transformation using the Isabelle logical framework.
... There are several papers and articles which investigate the expressive power of Datalog [9], the relation between Datalog and First-order Logic [1], inductive definitions in logics [5,4,13,12] and transitive closure and its role in inductive definitions [2]. ...
Article
In the last WSML phone conference we had a brief discussion about the expressivity of First-order Logic and Datalog resp. the relation between the expressiveness of those two languages. In particular, there has been some confusion around the description of the transitive closure R + of some binary relation R. In this short document, we want to clarify the situation and hope to remedy the confusion.
... The analysis of this paper can be extended to more expressive logic programming languages, such as [16,21,38,42], also to functional programming languages in the style of [3,40]. We deliberately chose our running examples to correspond to definitions of inductive or coinductive types in such languages. ...
... The analysis of this paper can be extended to more expressive logic programming languages, such as [16,21,38,42], also to functional programming languages in the style of [3,40]. We deliberately chose our running examples to correspond to definitions of inductive or coinductive types in such languages. ...
Article
Full-text available
Coinductive definitions, such as that of an infinite stream, may often be described by elegant logic programs, but ones for which SLD-refutation is of no value as SLD-derivations fall into infinite loops. Such definitions give rise to questions of lazy corecursive derivations and parallelism, as execution of such logic programs can have both recursive and corecursive features at once. Observational and coalgebraic semantics have been used to study them abstractly. The programming developments have often occurred separately and have usually been implementation-led. Here, we give a coherent semantics-led account of the issues, starting with abstract category theoretic semantics, developing coalgebra to characterize naturally arising trees and proceeding towards implementation of a new dialect, CoALP, of logic programming, characterised by guarded lazy corecursion and parallelism.
... Actually, such premises constitute a valued conjunction, also permitted as a top-level query (e.g., member (X, L) & member (X, M) non-deterministically returns rest lists of M whose first element also occurs in L). A special valued conjunction calling only relations to the left of "~" and having a single variable to its right (e.g., member(X, [ -1 , 2 , -3 , 4 , -5 ] ) , <(X,0) & X) can be viewed as an indefinite description or r/-expression (e.g., r/(x)[x e { -1 , 2 , -3 , 4 , -5 } A x < 0]), also provided in other relational/functional amalgamations (see [53]). It will be shown that certain RELFUN functions can be inverted by calling them nonground (by-value) on the right-hand side (rhs) of a generalized PROLOG is-primitive , mimicking relations (incl. ...
Article
Full-text available
Knowledge bases for real-world domains such as mechanical engineering require expressive and efficient representation and processing tools. We pursue a declarative-compilative approach to knowledge engineering. While Horn logic (as implemented in PROLOG) is well-suited for representing relational clauses, other kinds of declarative knowledge call for hybrid extensions: functional dependencies and higher-order knowledge should be modeled directly. Forward (bottom-up) reasoning should be integrated with backward (top-down) reasoning. Constraint propagation should be used wherever possible instead of search-intensive resolution. Taxonomic knowledge should be classified into an intuitive subsumption hierarchy. Our LISP-based tools provide directtranslators of these declarative representations into abstract machines such as an extended Warren Abstract Machine (WAM) and specialized inference engines that are interfaced to each other. More importantly, we provide source-to-sourcetransformers between various knowledge types, both for user convenience and machine efficiency. These formalisms with their translators and transformers have been developed as part of CoLab, acompilationlaboratory for studying what we call, respectively, ‘vertical’ and ‘horizontal’ compilation of knowledge, as well as for exploring the synergeticcolaboration of the knowledge representation formalisms. A case study in the realm of mechanical engineering has been an important driving force behind the development of CoLab. It will be used as the source of examples throughout the paper when discussing the enhanced formalisms, the hybrid representation architecture, and the compilers.
... In this paper, we propose to take a new look at recursive logic programs, that is, to approach them not from the point of view of first-order logic, but from the point of view of functional programming [9]. As an example, consider how a formal grammar generates the strings of a language. ...
Conference Paper
Full-text available
It has been one of the great challenges of neuro-symbolic integration to represent recursive logic programs using neural networks of finite size. In this paper, we propose to implement neural networks that can process recursive programs viewed as inductive definitions.
... There have been numerous attempts to resolve the mismatch between infinite derivations and greatest fixed point semantics, e.g., Jaume (2002); Lloyd (1987); Paulson and Smith (1989);. But infinite SLD derivations of both finite and infinite objects have not yet received a uniform semantics. ...
Conference Paper
Full-text available
We present an interactive heterogeneous theorem proving framework, which performs formal reasoning by arbitrarily mixing diagrammatic and sentential proof steps. We use Isabelle to enable formal reasoning with either traditional sentences or spider diagrams. We provide a mechanisation of the theory of abstract spider diagrams and establish a formal link between diagrammatic concepts and the existing theories in Isabelle/HOL.
Chapter
This paper describes the logic programming language GCLA II, its operational semantics and parts of its theoretical foundations. GCLA II is a generalization of the language GCLA (Generalized Horn Clause Language) augmented by a method to guide and constrain proof search. The method is based on specification of strategies in a meta language that is a sub language of GCLA itself. A GCLA II program is partitioned into two distinct parts. One is used to express the declarative content of the program, while the other is used to define the possible inferences made from this declarative knowledge. Although the intended use of the declarative part and the procedural parts are quite different, they can both be described in the formalism of partial inductive definitions. Thus we preserve a declarative reading of the program as a whole. In particular, given certain syntactical constraints on the program, the heuristics associated with proof search does not affect the declarative reading of the declarative part of the program at all. Experimental interpreters for the language have been implemented and work on a compiler from GCLA II to Prolog is almost completed.
Article
Full-text available
PrologPF is a parallelising compiler targeting a distributed system of general purpose workstations connected by a relatively low performance network. The source language extends standard Prolog with the integration of higher-order functions. The execution of a compiled PrologPF program proceeds in a similar manner to standard Prolog, but uses oracles in one of two modes. An oracle represents the sequence of clauses used to reach a given point in the problem search tree, and the same PrologPF executable can be used to build oracles, or follow oracles previously generated. The parallelisation strategy used by PrologPF proceeds in two phases, which this research shows can be interleaved. An initial phase searches the problem tree to a limited depth, recording the discovered incomplete paths. In the second phase these paths are allocated to the available processors in the network. Each processor follows its assigned paths and fully searches the referenced subtree, sending solutions back to a control processor. This research investigates the use of the technique with a one-time partitioning of the problem and no further scheduling communication, and with the recursive application of the partitioning technique to effect dynamic work reassignment. For a problem requiring all solutions to be found, execution completes when all the distributed processors have completed the search of their assigned subtrees. If one solution is required, the execution of all the path processors is terminated when the control processor receives the first solution. The presence of the extra-logical Prolog predicate cut in the user program conflicts with the use of oracles to represent valid open subtrees. PrologPF promotes the use of higher-order functional programming as an alternative to the use of cut. The combined language shows that functional support can be added as a consistent extension to standard Prolog.
Article
Full-text available
Coalgebra may be used to provide semantics for SLD-derivations, both finite and infinite. We first give such semantics to classical SLD-derivations, proving results such as adequacy, soundness and completeness. Then, based upon coalgebraic semantics, we propose a new sound and complete algorithm for parallel derivations. We analyse this new algorithm in terms of the Theory of Observables, and we prove soundness, completeness, correctness and full abstraction results.
Article
Full-text available
Formal logic is widely accepted as a program specification language in computing science. It is ideally suited to the representation of knowledge and the description of problems without regard to the choice of programming language. Its use as a specification language is compatible not only with conventional programming languages but also with programming languages based entirely on logic itself. In this paper the author investigates the relation that holds when both programs and program specifications are expressed in formal logic.
Book
To write specifications one needs to be able to define the data domains in which one is interested, such as numbers, lists, trees and graphs. One also needs to be able to define functions over these domains. It is desirable that the notation should be higher order, so that function spaces can themselves be treated as data domains. Finally, given the potential for confusion in specifications involving a large number of data types, it is a practical necessity that there should be a simple syntactic discipline that ensures that only well typed applications of functions can occur. A functional programming language with these properties is presented and its use as a specification tool is demonstrated on a series of examples. Although such a notation lacks the power of some imaginable specification languages (for example, in not allowing existential quantifiers), it has the advantage that specifications written in it are always executable. The strengths and weaknesses of this approach are discussed, and also the prospects for the use of purely functional languages in production programming.
Article
This paper presents an approach to the integration of two declarative programming styles, namely logic programming, as supported by the Prolog language, and higher-order functional programming, as available in languages like ML, Miranda, and LISP. The integration is obtained by adopting, as a high level intermediate language, a well founded first order logic language with equality, K-LEAF, for which an efficient computational model has been already developed, based on resolution and suited to Prolog-like implementation. The compilation of the highly-expressive higher-order user language IDEAL to K-LEAF can be represented by a formal deduction of universal properties of actual programs an axiomatization of a version of lambda-reduction, expressed in K-LEAF. The efficiency obtainable is of the same order of commercially available LISP systems.
Article
An automatic deduction system based on a modification of Gentzen's sequentzen system LJ is presented, and its use as the basis for a logic programming system is described. The system is a natural extension of Horn clause logic programming systems in that when all of the formulas in the input sequent are atomic, the behavior of the system mimics that of LUSH resolution systems. The use of such systems in program development systems and in database management systems is discussed.
Article
A logical language is presented which extends the syntax of positive Horn clauses by permitting implications in goals and in the bodies of clauses. The operational meaning of a goal which is an implication is given by the deduction theorem. That is, a goal such as 'D implies G', is satisfied by a program P if the goal G is satisfied by the larger union of the program P and the set D. If the formula D is the conjunction of a collection of universally quantified clauses, we interpret the goal 'D implies G' as a request to load the code in D prior to attempting G, and then unload that code after G succeeds or fails. This extended use of implication provides a logical explanation of parametric modules, some uses of Prolog's assert predicate, and certain kinds of abstract datatypes. Both a model-theory and proof-theory are presented for this logical language. The author shows how to build a possible-worlds (Kripke) model for programs by a fixed point construction and shows that the operational meaning of implication mentioned above is sound and complete for intuitionistic, but not classical, logic.
Article
A Prolog technology theorem prover (PTTP) is an extension of Prolog that is complete for the full first-order predicate calculus. It differs from Prolog in its use of unification with the occurs check for soundness, the model-elimination reduction rule that is added to Prolog inferences to make the inference system complete, and depth-first iterative-deepening search instead of unbounded depthfirst search to make the search strategy complete. A Prolog technology theorem prover has been implemented by an extended Prolog-to-LISP compiler that supports these additional features. It is capable of proving theorems in the full first-order predicate calculus at a rate of thousands of inferences per second.
Chapter
Inductive definitions of sets are often informally presented by giving some rules for generating elements of the set and then adding that an object is to be in the set only if it has been generated according to the rules. An equivalent formulation is to characterize the set as the smallest set closed under the rules. This chapter discusses monotone induction and its role in extensions of recursion theory. The chapter reviews some of the work on non-monotone induction and outlines the separate motivation that has led to its development. The chapter briefly considers inductive definitions in a more general context.