Hubbry Logo
Logic programmingLogic programmingMain
Open search
Logic programming
Community hub
Logic programming
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Logic programming
Logic programming
from Wikipedia

Logic programming is a programming, database and knowledge representation paradigm based on formal logic. A logic program is a set of sentences in logical form, representing knowledge about some problem domain. Computation is performed by applying logical reasoning to that knowledge, to solve problems in the domain. Major logic programming language families include Prolog, Answer Set Programming (ASP) and Datalog. In all of these languages, rules are written in the form of clauses:

A :- B1, ..., Bn.

and are read as declarative sentences in logical form:

A if B1 and ... and Bn.

A is called the head of the rule, B1, ..., Bn is called the body, and the Bi are called literals or conditions. When n = 0, the rule is called a fact and is written in the simplified form:

A.

Queries (or goals) have the same syntax as the bodies of rules and are commonly written in the form:

?- B1, ..., Bn.

In the simplest case of Horn clauses (or "definite" clauses), all of the A, B1, ..., Bn are atomic formulae of the form p(t1 ,..., tm), where p is a predicate symbol naming a relation, like "motherhood", and the ti are terms naming objects (or individuals). Terms include both constant symbols, like "charles", and variables, such as X, which start with an upper case letter.

Consider, for example, the following Horn clause program:

mother_child(elizabeth, charles).
father_child(charles, william).
father_child(charles, harry).
parent_child(X, Y) :- 
     mother_child(X, Y).
parent_child(X, Y) :- 
     father_child(X, Y).
grandparent_child(X, Y) :- 
     parent_child(X, Z), 
     parent_child(Z, Y).

Given a query, the program produces answers. For instance for a query ?- parent_child(X, william), the single answer is

X = charles

Various queries can be asked. For instance the program can be queried both to generate grandparents and to generate grandchildren. It can even be used to generate all pairs of grandchildren and grandparents, or simply to check if a given pair is such a pair:

grandparent_child(X, william).
X = elizabeth

?- grandparent_child(elizabeth, Y).
Y = william;
Y = harry.

?- grandparent_child(X, Y).
X = elizabeth
Y = william;
X = elizabeth
Y = harry.

?- grandparent_child(william, harry).
no
?- grandparent_child(elizabeth, harry).
yes

Although Horn clause logic programs are Turing complete,[1][2] for most practical applications, Horn clause programs need to be extended to "normal" logic programs with negative conditions. For example, the definition of sibling uses a negative condition, where the predicate = is defined by the clause X = X :

sibling(X, Y) :- 
     parent_child(Z, X), 
     parent_child(Z, Y), 
     not(X = Y).

Logic programming languages that include negative conditions have the knowledge representation capabilities of a non-monotonic logic.

In ASP and Datalog, logic programs have only a declarative reading, and their execution is performed by means of a proof procedure or model generator whose behaviour is not meant to be controlled by the programmer. However, in the Prolog family of languages, logic programs also have a procedural interpretation as goal-reduction procedures. From this point of view, clause A :- B1,...,Bn is understood as:

to solve A, solve B1, and ... and solve Bn.

Negative conditions in the bodies of clauses also have a procedural interpretation, known as negation as failure: A negative literal not B is deemed to hold if and only if the positive literal B fails to hold.

Much of the research in the field of logic programming has been concerned with trying to develop a logical semantics for negation as failure and with developing other semantics and other implementations for negation. These developments have been important, in turn, for supporting the development of formal methods for logic-based program verification and program transformation.

History

[edit]

The use of mathematical logic to represent and execute computer programs is also a feature of the lambda calculus, developed by Alonzo Church in the 1930s. However, the first proposal to use the clausal form of logic for representing computer programs was made by Cordell Green.[3] This used an axiomatization of a subset of LISP, together with a representation of an input-output relation, to compute the relation by simulating the execution of the program in LISP. Foster and Elcock's Absys, on the other hand, employed a combination of equations and lambda calculus in an assertional programming language that places no constraints on the order in which operations are performed.[4]

Logic programming, with its current syntax of facts and rules, can be traced back to debates in the late 1960s and early 1970s about declarative versus procedural representations of knowledge in artificial intelligence. Advocates of declarative representations were notably working at Stanford, associated with John McCarthy, Bertram Raphael and Cordell Green, and in Edinburgh, with John Alan Robinson (an academic visitor from Syracuse University), Pat Hayes, and Robert Kowalski. Advocates of procedural representations were mainly centered at MIT, under the leadership of Marvin Minsky and Seymour Papert.[5]

Although it was based on the proof methods of logic, Planner, developed by Carl Hewitt at MIT, was the first language to emerge within this proceduralist paradigm.[6] Planner featured pattern-directed invocation of procedural plans from goals (i.e. goal-reduction or backward chaining) and from assertions (i.e. forward chaining). The most influential implementation of Planner was the subset of Planner, called Micro-Planner, implemented by Gerry Sussman, Eugene Charniak and Terry Winograd. Winograd used Micro-Planner to implement the landmark, natural-language understanding program SHRDLU.[7] For the sake of efficiency, Planner used a backtracking control structure so that only one possible computation path had to be stored at a time. Planner gave rise to the programming languages QA4,[8] Popler,[9] Conniver,[10] QLISP,[11] and the concurrent language Ether.[12]

Hayes and Kowalski in Edinburgh tried to reconcile the logic-based declarative approach to knowledge representation with Planner's procedural approach. Hayes (1973) developed an equational language, Golux, in which different procedures could be obtained by altering the behavior of the theorem prover.[13]

In the meanwhile, Alain Colmerauer in Marseille was working on natural-language understanding, using logic to represent semantics and using resolution for question-answering. During the summer of 1971, Colmerauer invited Kowalski to Marseille, and together they discovered that the clausal form of logic could be used to represent formal grammars and that resolution theorem provers could be used for parsing. They observed that some theorem provers, like hyper-resolution,[14] behave as bottom-up parsers and others, like SL resolution (1971)[15] behave as top-down parsers.

It was in the following summer of 1972, that Kowalski, again working with Colmerauer, developed the procedural interpretation of implications in clausal form. It also became clear that such clauses could be restricted to definite clauses or Horn clauses, and that SL-resolution could be restricted (and generalised) to SLD resolution. Kowalski's procedural interpretation and SLD were described in a 1973 memo, published in 1974.[16]

Colmerauer, with Philippe Roussel, used the procedural interpretation as the basis of Prolog, which was implemented in the summer and autumn of 1972. The first Prolog program, also written in 1972 and implemented in Marseille, was a French question-answering system. The use of Prolog as a practical programming language was given great momentum by the development of a compiler by David H. D. Warren in Edinburgh in 1977. Experiments demonstrated that Edinburgh Prolog could compete with the processing speed of other symbolic programming languages such as Lisp.[17] Edinburgh Prolog became the de facto standard and strongly influenced the definition of ISO standard Prolog.

Logic programming gained international attention during the 1980s, when it was chosen by the Japanese Ministry of International Trade and Industry to develop the software for the Fifth Generation Computer Systems (FGCS) project. The FGCS project aimed to use logic programming to develop advanced Artificial Intelligence applications on massively parallel computers. Although the project initially explored the use of Prolog, it later adopted the use of concurrent logic programming, because it was closer to the FGCS computer architecture.

However, the committed choice feature of concurrent logic programming interfered with the language's logical semantics[18] and with its suitability for knowledge representation and problem solving applications. Moreover, the parallel computer systems developed in the project failed to compete with advances taking place in the development of more conventional, general-purpose computers. Together these two issues resulted in the FGCS project failing to meet its objectives. Interest in both logic programming and AI fell into world-wide decline.[19]

In the meanwhile, more declarative logic programming approaches, including those based on the use of Prolog, continued to make progress independently of the FGCS project. In particular, although Prolog was developed to combine declarative and procedural representations of knowledge, the purely declarative interpretation of logic programs became the focus for applications in the field of deductive databases. Work in this field became prominent around 1977, when Hervé Gallaire and Jack Minker organized a workshop on logic and databases in Toulouse.[20] The field was eventually renamed as Datalog.

This focus on the logical, declarative reading of logic programs was given further impetus by the development of constraint logic programming in the 1980s and Answer Set Programming in the 1990s. It is also receiving renewed emphasis in recent applications of Prolog[21]

The Association for Logic Programming (ALP) was founded in 1986 to promote Logic Programming. Its official journal until 2000, was The Journal of Logic Programming. Its founding editor-in-chief was J. Alan Robinson.[22] In 2001, the journal was renamed The Journal of Logic and Algebraic Programming, and the official journal of ALP became Theory and Practice of Logic Programming, published by Cambridge University Press.

Concepts

[edit]

Logic programs enjoy a rich variety of semantics and problem solving methods, as well as a wide range of applications in programming, databases, knowledge representation and problem solving.

Algorithm = Logic + Control

[edit]

The procedural interpretation of logic programs, which uses backward reasoning to reduce goals to subgoals, is a special case of the use of a problem-solving strategy to control the use of a declarative, logical representation of knowledge to obtain the behaviour of an algorithm. More generally, different problem-solving strategies can be applied to the same logical representation to obtain different algorithms. Alternatively, different algorithms can be obtained with a given problem-solving strategy by using different logical representations.[23]

The two main problem-solving strategies are backward reasoning (goal reduction) and forward reasoning, also known as top-down and bottom-up reasoning, respectively.

In the simple case of a propositional Horn clause program and a top-level atomic goal, backward reasoning determines an and-or tree, which constitutes the search space for solving the goal. The top-level goal is the root of the tree. Given any node in the tree and any clause whose head matches the node, there exists a set of child nodes corresponding to the sub-goals in the body of the clause. These child nodes are grouped together by an "and". The alternative sets of children corresponding to alternative ways of solving the node are grouped together by an "or".

Any search strategy can be used to search this space. Prolog uses a sequential, last-in-first-out, backtracking strategy, in which only one alternative and one sub-goal are considered at a time. For example, subgoals can be solved in parallel, and clauses can also be tried in parallel. The first strategy is called and-parallel and the second strategy is called or-parallel. Other search strategies, such as intelligent backtracking,[24] or best-first search to find an optimal solution,[25] are also possible.

In the more general, non-propositional case, where sub-goals can share variables, other strategies can be used, such as choosing the subgoal that is most highly instantiated or that is sufficiently instantiated so that only one procedure applies.[26] Such strategies are used, for example, in concurrent logic programming.

In most cases, backward reasoning from a query or goal is more efficient than forward reasoning. But sometimes with Datalog and Answer Set Programming, there may be no query that is separate from the set of clauses as a whole, and then generating all the facts that can be derived from the clauses is a sensible problem-solving strategy. Here is another example, where forward reasoning beats backward reasoning in a more conventional computation task, where the goal ?- fibonacci(n, Result) is to find the nth fibonacci number:

fibonacci(0, 0).
fibonacci(1, 1).

fibonacci(N, Result) :-
    N > 1,
    N1 is N - 1,
    N2 is N - 2,
    fibonacci(N1, F1),
    fibonacci(N2, F2),
    Result is F1 + F2.

Here the relation fibonacci(N, M) stands for the function fibonacci(N) = M, and the predicate N is Expression is Prolog notation for the predicate that instantiates the variable N to the value of Expression.

Given the goal of computing the fibonacci number of n, backward reasoning reduces the goal to the two subgoals of computing the fibonacci numbers of n-1 and n-2. It reduces the subgoal of computing the fibonacci number of n-1 to the two subgoals of computing the fibonacci numbers of n-2 and n-3, redundantly computing the fibonacci number of n-2. This process of reducing one fibonacci subgoal to two fibonacci subgoals continues until it reaches the numbers 0 and 1. Its complexity is of the order 2n. In contrast, forward reasoning generates the sequence of fibonacci numbers, starting from 0 and 1 without any recomputation, and its complexity is linear with respect to n.

Prolog cannot perform forward reasoning directly. But it can achieve the effect of forward reasoning within the context of backward reasoning by means of tabling: Subgoals are maintained in a table, along with their solutions. If a subgoal is re-encountered, it is solved directly by using the solutions already in the table, instead of re-solving the subgoals redundantly.[27]

Relationship with functional programming

[edit]

Logic programming can be viewed as a generalisation of functional programming, in which functions are a special case of relations.[28] For example, the function, mother(X) = Y, (every X has only one mother Y) can be represented by the relation mother(X, Y). In this respect, logic programs are similar to relational databases, which also represent functions as relations.

Compared with relational syntax, functional syntax is more compact for nested functions. For example, in functional syntax the definition of maternal grandmother can be written in the nested form:

maternal_grandmother(X) = mother(mother(X)).

The same definition in relational notation needs to be written in the unnested, flattened form:

maternal_grandmother(X, Y) :- mother(X, Z), mother(Z, Y).

However, nested syntax can be regarded as syntactic sugar for unnested syntax. Ciao Prolog, for example, transforms functional syntax into relational form and executes the resulting logic program using the standard Prolog execution strategy.[29] Moreover, the same transformation can be used to execute nested relations that are not functional. For example:

grandparent(X) := parent(parent(X)).
parent(X) := mother(X).
parent(X) := father(X).

mother(charles) := elizabeth.
father(charles) := phillip.
mother(harry) := diana.
father(harry) := charles.

?- grandparent(X,Y).
X = harry,
Y = elizabeth.
X = harry,
Y = phillip.

Relationship with relational programming

[edit]

The term relational programming has been used to cover a variety of programming languages that treat functions as a special case of relations. Some of these languages, such as miniKanren[28] and relational linear programming[30] are logic programming languages in the sense of this article.

However, the relational language RML is an imperative programming language [31] whose core construct is a relational expression, which is similar to an expression in first-order predicate logic.

Other relational programming languages are based on the relational calculus[32] or relational algebra.[33]

Semantics of Horn clause programs

[edit]

Viewed in purely logical terms, there are two approaches to the declarative semantics of Horn clause logic programs: One approach is the original logical consequence semantics, which understands solving a goal as showing that the goal is a theorem that is true in all models of the program.

In this approach, computation is theorem-proving in first-order logic; and both backward reasoning, as in SLD resolution, and forward reasoning, as in hyper-resolution, are correct and complete theorem-proving methods. Sometimes such theorem-proving methods are also regarded as providing a separate proof-theoretic (or operational) semantics for logic programs. But from a logical point of view, they are proof methods, rather than semantics.

The other approach to the declarative semantics of Horn clause programs is the satisfiability semantics, which understands solving a goal as showing that the goal is true (or satisfied) in some intended (or standard) model of the program. For Horn clause programs, there always exists such a standard model: It is the unique minimal model of the program.

Informally speaking, a minimal model is a model that, when it is viewed as the set of all (variable-free) facts that are true in the model, contains no smaller set of facts that is also a model of the program.

For example, the following facts represent the minimal model of the family relationships example in the introduction of this article. All other variable-free facts are false in the model:

mother_child(elizabeth, charles).
father_child(charles, william).
father_child(charles, harry).
parent_child(elizabeth, charles).
parent_child(charles, william).
parent_child(charles, harry).
grandparent_child(elizabeth, william).
grandparent_child(elizabeth, harry).

The satisfiability semantics also has an alternative, more mathematical characterisation as the least fixed point of the function that uses the rules in the program to derive new facts from existing facts in one step of inference.

Remarkably, the same problem-solving methods of forward and backward reasoning, which were originally developed for the logical consequence semantics, are equally applicable to the satisfiability semantics: Forward reasoning generates the minimal model of a Horn clause program, by deriving new facts from existing facts, until no new additional facts can be generated. Backward reasoning, which succeeds by reducing a goal to subgoals, until all subgoals are solved by facts, ensures that the goal is true in the minimal model, without generating the model explicitly.[34]

The difference between the two declarative semantics can be seen with the definitions of addition and multiplication in successor arithmetic, which represents the natural numbers 0, 1, 2, ... as a sequence of terms of the form 0, s(0), s(s(0)), .... In general, the term s(X) represents the successor of X, namely X + 1. Here are the standard definitions of addition and multiplication in functional notation:

     X + 0 = X.
     X + s(Y)    = s(X + Y). 
i.e. X + (Y + 1) = (X + Y) + 1

     X × 0 = 0.
     X × s(Y)    = X + (X × Y). 
i.e. X × (Y + 1) = X + (X × Y).

Here are the same definitions as a logic program, using add(X, Y, Z) to represent X + Y = Z, and multiply(X, Y, Z) to represent X × Y = Z:

add(X, 0, X).
add(X, s(Y), s(Z)) :- add(X, Y, Z).

multiply(X, 0, 0).
multiply(X, s(Y), W) :- multiply(X, Y, Z), add(X, Z, W).

The two declarative semantics both give the same answers for the same existentially quantified conjunctions of addition and multiplication goals. For example 2 × 2 = X has the solution X = 4; and X × X = X + X has two solutions X = 0 and X = 2:

?- multiply(s(s(0)), s(s(0)), X).
X = s(s(s(s(0)))).

?- multiply(X, X, Y), add(X, X, Y).
X = 0, Y = 0.
X = s(s(0)), Y = s(s(s(s(0)))).

However, with the logical-consequence semantics, there are non-standard models of the program, in which, for example, add(s(s(0)), s(s(0)), s(s(s(s(s(0)))))), i.e. 2 + 2 = 5 is true. But with the satisfiability semantics, there is only one model, namely the standard model of arithmetic, in which 2 + 2 = 5 is false.

In both semantics, the goal ?- add(s(s(0)), s(s(0)), s(s(s(s(s(0)))))) fails. In the satisfiability semantics, the failure of the goal means that the truth value of the goal is false. But in the logical consequence semantics, the failure means that the truth value of the goal is unknown.

Negation as failure

[edit]

Negation as failure (NAF), as a way of concluding that a negative condition not p holds by showing that the positive condition p fails to hold, was already a feature of early Prolog systems. The resulting extension of SLD resolution is called SLDNF. A similar construct, called "thnot", also existed in Micro-Planner.

The logical semantics of NAF was unresolved until Keith Clark[35] showed that, under certain natural conditions, NAF is an efficient, correct (and sometimes complete) way of reasoning with the logical consequence semantics using the completion of a logic program in first-order logic.

Completion amounts roughly to regarding the set of all the program clauses with the same predicate in the head, say:

A :- Body1.
...
A :- Bodyk.

as a definition of the predicate:

A iff (Body1 or ... or Bodyk)

where iff means "if and only if". The completion also includes axioms of equality, which correspond to unification. Clark showed that proofs generated by SLDNF are structurally similar to proofs generated by a natural deduction style of reasoning with the completion of the program.

Consider, for example, the following program:

should_receive_sanction(X, punishment) :- 
    is_a_thief(X),
    not should_receive_sanction(X, rehabilitation).
    
should_receive_sanction(X, rehabilitation) :-
    is_a_thief(X),
    is_a_minor(X),
    not is_violent(X).
    
is_a_thief(tom).

Given the goal of determining whether tom should receive a sanction, the first rule succeeds in showing that tom should be punished:

?- should_receive_sanction(tom, Sanction).
Sanction = punishment.

This is because tom is a thief, and it cannot be shown that tom should be rehabilitated. It cannot be shown that tom should be rehabilitated, because it cannot be shown that tom is a minor.

If, however, we receive new information that tom is indeed a minor, the previous conclusion that tom should be punished is replaced by the new conclusion that tom should be rehabilitated:

minor(tom).

?- should_receive_sanction(tom, Sanction).
Sanction = rehabilitation.

This property of withdrawing a conclusion when new information is added, is called non-monotonicity, and it makes logic programming a non-monotonic logic.

But, if we are now told that tom is violent, the conclusion that tom should be punished will be reinstated:

violent(tom).

?- should_receive_sanction(tom, Sanction).
Sanction = punishment.

The completion of this program is:

should_receive_sanction(X, Sanction) iff 
    Sanction = punishment, is_a_thief(X), 
    not should_receive_sanction(X, rehabilitation)
 or Sanction = rehabilitation, is_a_thief(X), is_a_minor(X),
    not is_violent(X).
    
is_a_thief(X) iff X = tom.
is_a_minor(X) iff X = tom.
is_violent(X) iff X = tom.

The notion of completion is closely related to John McCarthy's circumscription semantics for default reasoning,[36] and to Ray Reiter's closed world assumption.[37]

The completion semantics for negation is a logical consequence semantics, for which SLDNF provides a proof-theoretic implementation. However, in the 1980s, the satisfiability semantics became more popular for logic programs with negation. In the satisfiability semantics, negation is interpreted according to the classical definition of truth in an intended or standard model of the logic program.

In the case of logic programs with negative conditions, there are two main variants of the satisfiability semantics: In the well-founded semantics, the intended model of a logic program is a unique, three-valued, minimal model, which always exists. The well-founded semantics generalises the notion of inductive definition in mathematical logic.[38] XSB Prolog[39] implements the well-founded semantics using SLG resolution.[40]

In the alternative stable model semantics, there may be no intended models or several intended models, all of which are minimal and two-valued. The stable model semantics underpins answer set programming (ASP).

Both the well-founded and stable model semantics apply to arbitrary logic programs with negation. However, both semantics coincide for stratified logic programs. For example, the program for sanctioning thieves is (locally) stratified, and all three semantics for the program determine the same intended model:

should_receive_sanction(tom, punishment).
is_a_thief(tom).
is_a_minor(tom).
is_violent(tom).

Attempts to understand negation in logic programming have also contributed to the development of abstract argumentation frameworks.[41] In an argumentation interpretation of negation, the initial argument that tom should be punished because he is a thief, is attacked by the argument that he should be rehabilitated because he is a minor. But the fact that tom is violent undermines the argument that tom should be rehabilitated and reinstates the argument that tom should be punished.

Metalogic programming

[edit]

Metaprogramming, in which programs are treated as data, was already a feature of early Prolog implementations.[42][43] For example, the Edinburgh DEC10 implementation of Prolog included "an interpreter and a compiler, both written in Prolog itself".[43] The simplest metaprogram is the so-called "vanilla" meta-interpreter:

    solve(true).
    solve((B,C)):- solve(B),solve(C).
    solve(A):- clause(A,B),solve(B).

where true represents an empty conjunction, and (B,C) is a composite term representing the conjunction of B and C. The predicate clause(A,B) means that there is a clause of the form A :- B.

Metaprogramming is an application of the more general use of a metalogic or metalanguage to describe and reason about another language, called the object language.

Metalogic programming allows object-level and metalevel representations to be combined, as in natural language. For example, in the following program, the atomic formula attends(Person, Meeting) occurs both as an object-level formula, and as an argument of the metapredicates prohibited and approved.

prohibited(attends(Person, Meeting)) :- 
    not(approved(attends(Person, Meeting))).

should_receive_sanction(Person, scolding) :- attends(Person, Meeting), 
    lofty(Person), prohibited(attends(Person, Meeting)).
should_receive_sanction(Person, banishment) :- attends(Person, Meeting), 
    lowly(Person), prohibited(attends(Person, Meeting)).

approved(attends(alice, tea_party)).
attends(mad_hatter, tea_party).
attends(dormouse, tea_party).

lofty(mad_hatter).
lowly(dormouse).

?- should_receive_sanction(X,Y).
Person = mad_hatter,
Sanction = scolding.
Person = dormouse,
Sanction = banishment.

In his popular Introduction to Cognitive Science,[44] Paul Thagard includes logic and rules as alternative approaches to modelling human thinking. He argues that rules, which have the form IF condition THEN action, are "very similar" to logical conditionals, but they are simpler and have greater psychological plausibility (page 51). Among other differences between logic and rules, he argues that logic uses deduction, but rules use search (page 45) and can be used to reason either forward or backward (page 47). Sentences in logic "have to be interpreted as universally true", but rules can be defaults, which admit exceptions (page 44).

He states that "unlike logic, rule-based systems can also easily represent strategic information about what to do" (page 45). For example, "IF you want to go home for the weekend, and you have bus fare, THEN you can catch a bus". He does not observe that the same strategy of reducing a goal to subgoals can be interpreted, in the manner of logic programming, as applying backward reasoning to a logical conditional:

can_go(you, home) :- have(you, bus_fare), catch(you, bus).

All of these characteristics of rule-based systems - search, forward and backward reasoning, default reasoning, and goal-reduction - are also defining characteristics of logic programming. This suggests that Thagard's conclusion (page 56) that:

Much of human knowledge is naturally described in terms of rules, and many kinds of thinking such as planning can be modeled by rule-based systems.

also applies to logic programming.

Other arguments showing how logic programming can be used to model aspects of human thinking are presented by Keith Stenning and Michiel van Lambalgen in their book, Human Reasoning and Cognitive Science.[45] They show how the non-monotonic character of logic programs can be used to explain human performance on a variety of psychological tasks. They also show (page 237) that "closed–world reasoning in its guise as logic programming has an appealing neural implementation, unlike classical logic."

In The Proper Treatment of Events,[46] Michiel van Lambalgen and Fritz Hamm investigate the use of constraint logic programming to code "temporal notions in natural language by looking at the way human beings construct time".

Knowledge representation

[edit]

The use of logic to represent procedural knowledge and strategic information was one of the main goals contributing to the early development of logic programming. Moreover, it continues to be an important feature of the Prolog family of logic programming languages today. However, many applications of logic programming, including Prolog applications, increasingly focus on the use of logic to represent purely declarative knowledge. These applications include both the representation of general commonsense knowledge and the representation of domain specific expertise.

Commonsense includes knowledge about cause and effect, as formalised, for example, in the situation calculus, event calculus and action languages. Here is a simplified example, which illustrates the main features of such formalisms. The first clause states that a fact holds immediately after an event initiates (or causes) the fact. The second clause is a frame axiom, which states that a fact that holds at a time continues to hold at the next time unless it is terminated by an event that happens at the time. This formulation allows more than one event to occur at the same time:

holds(Fact, Time2) :- 
    happens(Event, Time1),
    Time2 is Time1 + 1,
    initiates(Event, Fact).
     
holds(Fact, Time2) :- 
	happens(Event, Time1),
    Time2 is Time1 + 1,
    holds(Fact, Time1),
    not(terminated(Fact, Time1)).

terminated(Fact, Time) :-
   happens(Event, Time),
   terminates(Event, Fact).

Here holds is a meta-predicate, similar to solve above. However, whereas solve has only one argument, which applies to general clauses, the first argument of holds is a fact and the second argument is a time (or state). The atomic formula holds(Fact, Time) expresses that the Fact holds at the Time. Such time-varying facts are also called fluents. The atomic formula happens(Event, Time) expresses that the Event happens at the Time.

The following example illustrates how these clauses can be used to reason about causality in a toy blocks world. Here, in the initial state at time 0, a green block is on a table and a red block is stacked on the green block (like a traffic light). At time 0, the red block is moved to the table. At time 1, the green block is moved onto the red block. Moving an object onto a place terminates the fact that the object is on any place, and initiates the fact that the object is on the place to which it is moved:

holds(on(green_block, table), 0).
holds(on(red_block, green_block), 0).

happens(move(red_block, table), 0).
happens(move(green_block, red_block), 1).

initiates(move(Object, Place), on(Object, Place)).
terminates(move(Object, Place2), on(Object, Place1)).

?- holds(Fact, Time).

Fact = on(green_block,table),
Time = 0.
Fact = on(red_block,green_block),
Time = 0.
Fact = on(green_block,table),
Time = 1.
Fact = on(red_block,table),
Time = 1.
Fact = on(green_block,red_block),
Time = 2.
Fact = on(red_block,table),
Time = 2.

Forward reasoning and backward reasoning generate the same answers to the goal holds(Fact, Time). But forward reasoning generates fluents progressively in temporal order, and backward reasoning generates fluents regressively, as in the domain-specific use of regression in the situation calculus.[47]

Logic programming has also proved to be useful for representing domain-specific expertise in expert systems.[48] But human expertise, like general-purpose commonsense, is mostly implicit and tacit, and it is often difficult to represent such implicit knowledge in explicit rules. This difficulty does not arise, however, when logic programs are used to represent the existing, explicit rules of a business organisation or legal authority.

For example, here is a representation of a simplified version of the first sentence of the British Nationality Act, which states that a person who is born in the UK becomes a British citizen at the time of birth if a parent of the person is a British citizen at the time of birth:

initiates(birth(Person), citizen(Person, uk)):-
    time_of(birth(Person), Time),
    place_of(birth(Person), uk),
    parent_child(Another_Person, Person),
    holds(citizen(Another_Person, uk), Time).

Historically, the representation of a large portion of the British Nationality Act as a logic program in the 1980s[49] was "hugely influential for the development of computational representations of legislation, showing how logic programming enables intuitively appealing representations that can be directly deployed to generate automatic inferences".[50]

More recently, the PROLEG system,[51] initiated in 2009 and consisting of approximately 2500 rules and exceptions of civil code and supreme court case rules in Japan, has become possibly the largest legal rule base in the world.[52]

Variants and extensions

[edit]

Prolog

[edit]

The SLD resolution rule of inference is neutral about the order in which subgoals in the bodies of clauses can be selected for solution. For the sake of efficiency, Prolog restricts this order to the order in which the subgoals are written. SLD is also neutral about the strategy for searching the space of SLD proofs. Prolog searches this space, top-down, depth-first, trying different clauses for solving the same (sub)goal in the order in which the clauses are written.

This search strategy has the advantage that the current branch of the tree can be represented efficiently by a stack. When a goal clause at the top of the stack is reduced to a new goal clause, the new goal clause is pushed onto the top of the stack. When the selected subgoal in the goal clause at the top of the stack cannot be solved, the search strategy backtracks, removing the goal clause from the top of the stack, and retrying the attempted solution of the selected subgoal in the previous goal clause using the next clause that matches the selected subgoal.

Backtracking can be restricted by using a subgoal, called cut, written as !, which always succeeds but cannot be backtracked. Cut can be used to improve efficiency, but can also interfere with the logical meaning of clauses. In many cases, the use of cut can be replaced by negation as failure. In fact, negation as failure can be defined in Prolog, by using cut, together with any literal, say fail, that unifies with the head of no clause:

not(P) :- P, !, fail.
not(P).

Prolog provides other features, in addition to cut, that do not have a logical interpretation. These include the built-in predicates assert and retract for destructively updating the state of the program during program execution.

For example, the toy blocks world example above can be implemented without frame axioms using destructive change of state:

on(green_block, table).
on(red_block, green_block).

move(Object, Place2) :- 
	retract(on(Object, Place1)), 
	assert(on(Object, Place2).

The sequence of move events and the resulting locations of the blocks can be computed by executing the query:

?- move(red_block, table), move(green_block, red_block), on(Object, Place).

Object = red_block,
Place = table.
Object = green_block,
Place = red_block.

Various extensions of logic programming have been developed to provide a logical framework for such destructive change of state.[53][54][55]

The broad range of Prolog applications, both in isolation and in combination with other languages is highlighted in the Year of Prolog Book,[21] celebrating the 50 year anniversary of Prolog in 2022.

Prolog has also contributed to the development of other programming languages, including ALF, Fril, Gödel, Mercury, Oz, Ciao, Visual Prolog, XSB, and λProlog.

Constraint logic programming

[edit]

Constraint logic programming (CLP) combines Horn clause logic programming with constraint solving. It extends Horn clauses by allowing some predicates, declared as constraint predicates, to occur as literals in the body of a clause. Constraint predicates are not defined by the facts and rules in the program, but are predefined by some domain-specific model-theoretic structure or theory.

Procedurally, subgoals whose predicates are defined by the program are solved by goal-reduction, as in ordinary logic programming, but constraints are simplified and checked for satisfiability by a domain-specific constraint-solver, which implements the semantics of the constraint predicates. An initial problem is solved by reducing it to a satisfiable conjunction of constraints.

Interestingly, the first version of Prolog already included a constraint predicate dif(term1, term2), from Philippe Roussel's 1972 PhD thesis, which succeeds if both of its arguments are different terms, but which is delayed if either of the terms contains a variable.[52]

The following constraint logic program represents a toy temporal database of john's history as a teacher:

teaches(john, hardware, T) :- 1990  T, T < 1999.
teaches(john, software, T) :- 1999  T, T < 2005.
teaches(john, logic, T) :- 2005  T, T  2012.
rank(john, instructor, T) :- 1990  T, T < 2010.
rank(john, professor, T) :- 2010  T, T < 2014.

Here and < are constraint predicates, with their usual intended semantics. The following goal clause queries the database to find out when john both taught logic and was a professor:

?- teaches(john, logic, T), rank(john, professor, T).

The solution 2010 ≤ T, T ≤ 2012 results from simplifying the constraints 2005 ≤ T, T ≤ 2012, 2010 ≤ T, T < 2014.

Constraint logic programming has been used to solve problems in such fields as civil engineering, mechanical engineering, digital circuit verification, automated timetabling, air traffic control, and finance. It is closely related to abductive logic programming.

Datalog

[edit]

Datalog is a database definition language, which combines a relational view of data, as in relational databases, with a logical view, as in logic programming.

Relational databases use a relational calculus or relational algebra, with relational operations, such as union, intersection, set difference and cartesian product to specify queries, which access a database. Datalog uses logical connectives, such as or, and and not in the bodies of rules to define relations as part of the database itself.

It was recognized early in the development of relational databases that recursive queries cannot be expressed in either relational algebra or relational calculus, and that this defficiency can be remedied by introducing a least-fixed-point operator.[56][57] In contrast, recursive relations can be defined naturally by rules in logic programs, without the need for any new logical connectives or operators.

Datalog differs from more general logic programming by having only constants and variables as terms. Moreover, all facts are variable-free, and rules are restricted, so that if they are executed bottom-up, then the derived facts are also variable-free.

For example, consider the family database:

mother_child(elizabeth, charles).
father_child(charles, william).
father_child(charles, harry).
parent_child(X, Y) :- 
     mother_child(X, Y).
parent_child(X, Y) :- 
     father_child(X, Y).
ancestor_descendant(X, Y) :- 
     parent_child(X, X).
ancestor_descendant(X, Y) :- 
     ancestor_descendant(X, Z), 
     ancestor_descendant(Z, Y).

Bottom-up execution derives the following set of additional facts and terminates:

parent_child(elizabeth, charles).
parent_child(charles, william).
parent_child(charles, harry).

ancestor_descendant(elizabeth, charles).
ancestor_descendant(charles, william).
ancestor_descendant(charles, harry).

ancestor_descendant(elizabeth, william).
ancestor_descendant(elizabeth, harry).

Top-down execution derives the same answers to the query:

?- ancestor_descendant(X, Y).

But then it goes into an infinite loop. However, top-down execution with tabling gives the same answers and terminates without looping.

Answer set programming

[edit]

Like Datalog, Answer Set programming (ASP) is not Turing-complete. Moreover, instead of separating goals (or queries) from the program to be used in solving the goals, ASP treats the whole program as a goal, and solves the goal by generating a stable model that makes the goal true. For this purpose, it uses the stable model semantics, according to which a logic program can have zero, one or more intended models. For example, the following program represents a degenerate variant of the map colouring problem of colouring two countries red or green:

country(oz).
country(iz).
adjacent(oz, iz).
colour(C, red) :- country(C), not(colour(C, green)).
colour(C, green) :- country(C), not(colour(C, red)).

The problem has four solutions represented by four stable models:

country(oz). country(iz). adjacent(oz, iz). colour(oz, red).   colour(iz, red).

country(oz). country(iz). adjacent(oz, iz). colour(oz, green). colour(iz, green).

country(oz). country(iz). adjacent(oz, iz). colour(oz, red).   colour(iz, green).

country(oz). country(iz). adjacent(oz, iz). colour(oz, green). colour(iz, red).

To represent the standard version of the map colouring problem, we need to add a constraint that two adjacent countries cannot be coloured the same colour. In ASP, this constraint can be written as a clause of the form:

:- country(C1), country(C2), adjacent(C1, C2), colour(C1, X), colour(C2, X).

With the addition of this constraint, the problem now has only two solutions:

country(oz). country(iz). adjacent(oz, iz). colour(oz, red).   colour(iz, green).

country(oz). country(iz). adjacent(oz, iz). colour(oz, green). colour(iz, red).

The addition of constraints of the form :- Body. eliminates models in which Body is true.

Confusingly, constraints in ASP are different from constraints in CLP. Constraints in CLP are predicates that qualify answers to queries (and solutions of goals). Constraints in ASP are clauses that eliminate models that would otherwise satisfy goals. Constraints in ASP are like integrity constraints in databases.

This combination of ordinary logic programming clauses and constraint clauses illustrates the generate-and-test methodology of problem solving in ASP: The ordinary clauses define a search space of possible solutions, and the constraints filter out unwanted solutions.[58]

Most implementations of ASP proceed in two steps: First they instantiate the program in all possible ways, reducing it to a propositional logic program (known as grounding). Then they apply a propositional logic problem solver, such as the DPLL algorithm or a Boolean SAT solver. However, some implementations, such as s(CASP)[59] use a goal-directed, top-down, SLD resolution-like procedure without grounding.

Abductive logic programming

[edit]

Abductive logic programming[60] (ALP), like CLP, extends normal logic programming by allowing the bodies of clauses to contain literals whose predicates are not defined by clauses. In ALP, these predicates are declared as abducible (or assumable), and are used as in abductive reasoning to explain observations, or more generally to add new facts to the program (as assumptions) to solve goals.

For example, suppose we are given an initial state in which a red block is on a green block on a table at time 0:

holds(on(green_block, table), 0).
holds(on(red_block, green_block), 0).

Suppose we are also given the goal:

?- holds(on(green_block,red_block), 3), holds(on(red_block,table), 3).

The goal can represent an observation, in which case a solution is an explanation of the observation. Or the goal can represent a desired future state of affairs, in which case a solution is a plan for achieving the goal.[61]

We can use the rules for cause and effect presented earlier to solve the goal, by treating the happens predicate as abducible:

holds(Fact, Time2) :- 
    happens(Event, Time1),
    Time2 is Time1 + 1,
    initiates(Event, Fact).
     
holds(Fact, Time2) :- 
	happens(Event, Time1),
    Time2 is Time1 + 1,
    holds(Fact, Time1),
    not(terminated(Fact, Time1)).
    
terminated(Fact, Time) :-
   happens(Event, Time),
   terminates(Event, Fact).

initiates(move(Object, Place), on(Object, Place)).
terminates(move(Object, Place2), on(Object, Place1)).

ALP solves the goal by reasoning backwards and adding assumptions to the program, to solve abducible subgoals. In this case there are many alternative solutions, including:

happens(move(red_block, table), 0).
happens(tick, 1).
happens(move(green_block, red_block), 2).
happens(tick,0).
happens(move(red_block, table), 1).
happens(move(green_block, red_block), 2).
happens(move(red_block, table), 0).
happens(move(green_block, red_block), 1).
happens(tick, 2).

Here tick is an event that marks the passage of time without initiating or terminating any fluents.

There are also solutions in which the two move events happen at the same time. For example:

happens(move(red_block, table), 0).
happens(move(green_block, red_block), 0).
happens(tick, 1).
happens(tick, 2).

Such solutions, if not desired, can be removed by adding an integrity constraint, which is like a constraint clause in ASP:

:- happens(move(Block1, Place), Time), happens(move(Block2, Block1), Time).

Abductive logic programming has been used for fault diagnosis, planning, natural language processing and machine learning. It has also been used to interpret negation as failure as a form of abductive reasoning.[62]

Inductive logic programming

[edit]

Inductive logic programming (ILP) is an approach to machine learning that induces logic programs as hypothetical generalisations of positive and negative examples. Given a logic program representing background knowledge and positive examples together with constraints representing negative examples, an ILP system induces a logic program that generalises the positive examples while excluding the negative examples.

ILP is similar to ALP, in that both can be viewed as generating hypotheses to explain observations, and as employing constraints to exclude undesirable hypotheses. But in ALP the hypotheses are variable-free facts, and in ILP the hypotheses are general rules.[63][64]

For example, given only background knowledge of the mother_child and father_child relations, and suitable examples of the grandparent_child relation, current ILP systems can generate the definition of grandparent_child, inventing an auxiliary predicate, which can be interpreted as the parent_child relation:[65]

grandparent_child(X, Y):- auxiliary(X, Z), auxiliary(Z, Y).
auxiliary(X, Y):- mother_child(X, Y).
auxiliary(X, Y):- father_child(X, Y).

Stuart Russell[66] has referred to such invention of new concepts as the most important step needed for reaching human-level AI.

Recent work in ILP, combining logic programming, learning and probability, has given rise to the fields of statistical relational learning and probabilistic inductive logic programming.

Concurrent logic programming

[edit]

Concurrent logic programming integrates concepts of logic programming with concurrent programming. Its development was given a big impetus in the 1980s by its choice for the systems programming language of the Japanese Fifth Generation Project (FGCS).[67]

A concurrent logic program is a set of guarded Horn clauses of the form:

H :- G1, ..., Gn | B1, ..., Bn.

The conjunction G1, ... , Gn is called the guard of the clause, and | is the commitment operator. Declaratively, guarded Horn clauses are read as ordinary logical implications:

H if G1 and ... and Gn and B1 and ... and Bn.

However, procedurally, when there are several clauses whose heads H match a given goal, then all of the clauses are executed in parallel, checking whether their guards G1, ... , Gn hold. If the guards of more than one clause hold, then a committed choice is made to one of the clauses, and execution proceeds with the subgoals B1, ..., Bn of the chosen clause. These subgoals can also be executed in parallel. Thus concurrent logic programming implements a form of "don't care nondeterminism", rather than "don't know nondeterminism".

For example, the following concurrent logic program defines a predicate shuffle(Left, Right, Merge), which can be used to shuffle two lists Left and Right, combining them into a single list Merge that preserves the ordering of the two lists Left and Right:

shuffle([], [], []).
shuffle(Left, Right, Merge) :-
    Left = [First | Rest] |
    Merge = [First | ShortMerge],
    shuffle(Rest, Right, ShortMerge).
shuffle(Left, Right, Merge) :-
    Right = [First | Rest] |
    Merge = [First | ShortMerge],
    shuffle(Left, Rest, ShortMerge).

Here, [] represents the empty list, and [Head | Tail] represents a list with first element Head followed by list Tail, as in Prolog. (Notice that the first occurrence of | in the second and third clauses is the list constructor, whereas the second occurrence of | is the commitment operator.) The program can be used, for example, to shuffle the lists [ace, queen, king] and [1, 4, 2] by invoking the goal clause:

shuffle([ace, queen, king], [1, 4, 2], Merge).

The program will non-deterministically generate a single solution, for example Merge = [ace, queen, 1, king, 4, 2].

Carl Hewitt has argued[68] that, because of the indeterminacy of concurrent computation, concurrent logic programming cannot implement general concurrency. However, according to the logical semantics, any result of a computation of a concurrent logic program is a logical consequence of the program, even though not all logical consequences can be derived.

Concurrent constraint logic programming

[edit]

Concurrent constraint logic programming[69] combines concurrent logic programming and constraint logic programming, using constraints to control concurrency. A clause can contain a guard, which is a set of constraints that may block the applicability of the clause. When the guards of several clauses are satisfied, concurrent constraint logic programming makes a committed choice to use only one.

Higher-order logic programming

[edit]

Several researchers have extended logic programming with higher-order programming features derived from higher-order logic, such as predicate variables. Such languages include the Prolog extensions HiLog[70] and λProlog.[71]

Linear logic programming

[edit]

Basing logic programming within linear logic has resulted in the design of logic programming languages that are considerably more expressive than those based on classical logic. Horn clause programs can only represent state change by the change in arguments to predicates. In linear logic programming, one can use the ambient linear logic to support state change. Some early designs of logic programming languages based on linear logic include LO,[72] Lolli,[73] ACL,[74] and Forum.[75] Forum provides a goal-directed interpretation of all linear logic.

Object-oriented logic programming

[edit]

F-logic[76] extends logic programming with objects and the frame syntax.

Logtalk[77] extends the Prolog programming language with support for objects, protocols, and other OOP concepts. It supports most standard-compliant Prolog systems as backend compilers.

Transaction logic programming

[edit]

Transaction logic[53] is an extension of logic programming with a logical theory of state-modifying updates. It has both a model-theoretic semantics and a procedural one. An implementation of a subset of Transaction logic is available in the Flora-2[78] system. Other prototypes are also available.

See also

[edit]

Citations

[edit]
  1. ^ Tärnlund, S.Å. (1977). "Horn clause computability". BIT Numerical Mathematics. 17 (2): 215–226. doi:10.1007/BF01932293. S2CID 32577496.
  2. ^ Andréka, H.; Németi, I. (1978). "The generalised completeness of Horn predicate-logic as a programming language". Acta Cybernetica. 4 (1): 3–10.
  3. ^ Green, Cordell. Application of Theorem Proving to Problem Solving (PDF). IJCAI 1969.
  4. ^ Foster, J.M.; Elcock, E.W. (1969). ABSYS 1: An Incremental Compiler for Assertions: an Introduction. Fourth Annual Machine Intelligence Workshop. Machine Intelligence. Vol. 4. Edinburgh, UK: Edinburgh University Press. pp. 423–429.
  5. ^ Kowalski, R. A. (1988). "The early years of logic programming" (PDF). Communications of the ACM. 31: 38–43. doi:10.1145/35043.35046. S2CID 12259230.
  6. ^ Hewitt, Carl. Planner: A Language for Proving Theorems in Robots (PDF). IJCAI 1969.
  7. ^ Winograd, Terry (1972). "Understanding natural language". Cognitive Psychology. 3 (1): 1–191. doi:10.1016/0010-0285(72)90002-3.
  8. ^ Jeff Rulifson; Jan Derksen; Richard Waldinger (November 1973). QA4, A Procedural Calculus for Intuitive Reasoning (PDF) (Technical report). SRI AI Center Technical Note 73.
  9. ^ Davies, J.M., 1971. POPLER: a POP-2 planner. Edinburgh University, Department of Machine Intelligence and Perception.
  10. ^ McDermott, D.V.; Sussman, G.J. (May 1972). The Conniver reference manual (Technical report). Artificial Intelligence Memo No. 259.
  11. ^ Reboh, R.; Sacerdoti, E.D. (August 1973). A preliminary QLISP manual (Technical report). Artificial Intelligence Center, SRI International.
  12. ^ Kornfeld, W.A.; Hewitt, C.E. (1981). "The scientific community metaphor". IEEE Transactions on Systems, Man, and Cybernetics. 11 (1): 24–33. doi:10.1109/TSMC.1981.4308575. hdl:1721.1/5693. S2CID 1322857.
  13. ^ Hayes, Pat (1973). "Computation and Deduction". Proceedings of the 2nd MFCS Symposium. Czechoslovak Academy of Sciences. pp. 105–118.
  14. ^ Robinson, J. (1965). "Automatic deduction with hyper-resolution". International Journal of Computer Mathematics. 1 (3): 227–234. doi:10.2307/2272384. JSTOR 2272384.
  15. ^ Kowalski, Robert; Kuehner, Donald (1971). "Linear Resolution with Selection Function" (PDF). Artificial Intelligence. 2 (3–4): 227–260. doi:10.1016/0004-3702(71)90012-9.
  16. ^ Kowalski, Robert (1973). "Predicate Logic as a Programming Language" (PDF). Department of Artificial Intelligence, Edinburgh University. Memo 70. Also in Proceedings IFIP Congress, Stockholm, North Holland Publishing Co., 1974, pp. 569–574.
  17. ^ Warren, D.H.; Pereira, L.M.; Pereira, F. (1977). "Prolog-the language and its implementation compared with Lisp". ACM SIGPLAN Notices. 12 (8): 109–115. doi:10.1145/872734.806939.
  18. ^ Ueda, K., 2018. Logic/constraint programming and concurrency: The hard-won lessons of the fifth generation computer project. Science of Computer Programming, 164, pp.3-17.
  19. ^ H.P. Newquist, 2020. The Brain Makers: The History Of Artificial Intelligence. The Relayer Group.
  20. ^ Gallaire, Hervé; Minker, John 'Jack', eds. (1978), "Logic and Data Bases, Symposium on Logic and Data Bases, Centre d'études et de recherches de Toulouse, 1977", Advances in Data Base Theory, New York: Plenum Press, ISBN 978-0-306-40060-5.
  21. ^ a b Warren, D.S. (2023). "Introduction to Prolog". In Warren, D.S.; Dahl, V.; Eiter, T.; Hermenegildo, M.V.; Kowalski, R.; Rossi, F. (eds.). Prolog: The Next 50 Years. Lecture Notes in Computer Science(). Vol. 13900. Springer, Cham. pp. 3–19. doi:10.1007/978-3-031-35254-6_1. ISBN 978-3-031-35253-9.
  22. ^ Robinson, J. Alan (2001). "Invited Editorial". Theory and Practice of Logic Programming. 1 (1). Cambridge University Press: 1. doi:10.1017/s1471068400000028.
  23. ^ R.A.Kowalski (July 1979). "Algorithm=Logic + Control". Communications of the ACM. 22 (7): 424–436. doi:10.1145/359131.359136. S2CID 2509896.
  24. ^ Bruynooghe, M.; Pereira, L.M. (1984). "Deduction revision by intelligent backtracking". Implementations of Prolog. Chichester, England: Ellis Horwood. pp. 194–215.
  25. ^ Nakamura, K. (July 1985). Heuristic Prolog: logic program execution by heuristic search. Conference on Logic Programming. Berlin, Heidelberg: Springer Berlin Heidelberg. pp. 148–155.
  26. ^ Genesereth, M.R.; Ginsberg, M.L. (1985). "Logic programming". Communications of the ACM. 28 (9): 933–941. doi:10.1145/4284.4287. S2CID 15527861.
  27. ^ Swift, T.; Warren, D.S. (January 2012). "XSB: Extending Prolog with tabled logic programming". Theory and Practice of Logic Programming. 12 (1–2): 157–187. arXiv:1012.5123. doi:10.1017/S1471068411000500. S2CID 6153112.
  28. ^ a b Daniel Friedman; William Byrd; Oleg Kiselyov; Jason Hemann (2018). The Reasoned Schemer, Second Edition. The MIT Press.
  29. ^ A. Casas, D. Cabeza, M. V. Hermenegildo. A Syntactic Approach to Combining Functional Notation, Lazy Evaluation and Higher-Order in LP Systems. The 8th International Symposium on Functional and Logic Programming (FLOPS'06), pages 142-162, April 2006.
  30. ^ Kersting, K., Mladenov, M. and Tokmakov, P., 2017. Relational linear programming. Artificial Intelligence, 244, pp.188-216.
  31. ^ Beyer, D., 2006, May. Relational programming with CrocoPat. In Proceedings of the 28th International Conference on Software engineering (pp. 807-810).
  32. ^ MacLennan, Bruce James (March 1983). Wexelblat, Richard L. (ed.). "Overview of relational programming". ACM SIGPLAN Notices. 18 (3). New York, NY: Association for Computing Machinery: 36–45. doi:10.1145/988209.988213. hdl:10945/29034. ISSN 0362-1340. OCLC 25073822. Retrieved 8 May 2025.
  33. ^ Behnke, R., Berghammer, R., Meyer, E. and Schneider, P., 1998. RELVIEW—A system for calculating with relations and relational programming. In Fundamental Approaches to Software Engineering: First International Conference, FASE'98 Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS'98 Lisbon, Portugal, March 28–April 4, 1998 Proceedings 1 (pp. 318-321). Springer Berlin Heidelberg.
  34. ^ Van Emden, M.H.; Kowalski, R.A. (October 1976). "The semantics of predicate logic as a programming language". Journal of the ACM. 23 (4): 733–742. doi:10.1145/321978.321991. S2CID 11048276.
  35. ^ Clark, K.L. (1977). "Negation as Failure". Logic and Data Bases. Boston, MA: Springer US. pp. 293–322. doi:10.1007/978-1-4684-3384-5_11. ISBN 978-1-4684-3386-9.
  36. ^ Gelfond, M.; Przymusinska, H.; Przymusinski, T. (1989). "On the relationship between circumscription and negation as failure". Artificial Intelligence. 38 (1): 75–94. doi:10.1016/0004-3702(89)90068-4.
  37. ^ Shepherdson, J.C. (1984). "Negation as failure: a comparison of Clark's completed data base and Reiter's closed world assumption". The Journal of Logic Programming. 1 (1): 51–79. doi:10.1016/0743-1066(84)90023-2.
  38. ^ Denecker, M.; Ternovska, E. (2008). "A logic of nonmonotone inductive definitions". ACM Transactions on Computational Logic. 9 (2): 14:1–14:52. arXiv:cs/0501025. doi:10.1145/1342991.1342998. S2CID 13156469.
  39. ^ Rao, P.; Sagonas, K.; Swift, T.; Warren, D.S.; Freire, J. (July 28–31, 1997). XSB: A system for efficiently computing well-founded semantics. Logic Programming And Nonmonotonic Reasoning: 4th International Conference, LPNMR'97. Dagstuhl Castle, Germany: Springer Berlin Heidelberg. pp. 430–440. doi:10.1007/3-540-63255-7_33.
  40. ^ W. Chen; D. S. Warren (January 1996). "Tabled Evaluation with Delaying for General Logic Programs". Journal of the ACM. 43 (1): 20–74. doi:10.1145/227595.227597. S2CID 7041379.
  41. ^ Phan Minh Dung (1995). "On the acceptability of arguments and its fundamental role in nonmonotonic reasoning, logic programming, and n–person games". Artificial Intelligence. 77 (2): 321–357. doi:10.1016/0004-3702(94)00041-X.
  42. ^ Colmerauer, A. and Roussel, P., 1996. The birth of Prolog. In History of programming languages---II (pp. 331-367).
  43. ^ a b Warren, D.H., Pereira, L.M. and Pereira, F., 1977. Prolog-the language and its implementation compared with Lisp. ACM SIGPLAN Notices, 12(8), pp.109-115.
  44. ^ Thagard, Paul (2005). Mind: Introduction to Cognitive Science. The MIT Press. p. 11. ISBN 9780262701099.https://www.google.co.uk/books/edition/Mind_second_edition/gjcR1U2HT7kC?hl=en&gbpv=1&pg=PP11&printsec=frontcover
  45. ^ Stenning, Keith; van Lambalgen, Michiel (2008). Human reasoning and cognitive science. MIT Press. ISBN 978-0-262-19583-6.https://philpapers.org/archive/STEHRA-5.pdf
  46. ^ Van Lambalgen, M. and Hamm, F., 2008. The proper treatment of events. John Wiley & Sons. https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=3126320bb6e37ca3727fed404828b53fc56ff063
  47. ^ Reiter, R., 1991. The frame problem in the situation calculus: A simple solution (sometimes) and a completeness result for goal regression. Artificial and Mathematical Theory of Computation, 3.
  48. ^ Merritt, D., 2012. Building expert systems in Prolog. Springer Science & Business Media. https://ds.amu.edu.et/xmlui/bitstream/handle/123456789/4434/%28Text%20Book%29%20Building%20Expert%20Systems%20in%20Prolog.pdf?sequence=1&isAllowed=y
  49. ^ Sergot, M.J.; Sadri, F.; Kowalski, R.A.; Kriwaczek, F.; Hammond, P; Cory, H.T. (1986). "The British Nationality Act as a logic program" (PDF). Communications of the ACM. 29 (5): 370–386. doi:10.1145/5689.5920. S2CID 5665107.
  50. ^ Prakken, H.; Sartor, G. (October 2015). "Law and logic: a review from an argumentation perspective" (PDF). Artificial Intelligence. 227: 214–245. doi:10.1016/j.artint.2015.06.005. S2CID 4261497.
  51. ^ Satoh, K., 2023. PROLEG: Practical legal reasoning system. In Prolog: The Next 50 Years (pp. 277-283). Cham: Springer Nature Switzerland.
  52. ^ a b Körner, Philipp; Leuschel, Michael; Barbosa, João; Costa, Vítor Santos; Dahl, Verónica; Hermenegildo, Manuel V.; Morales, Jose F.; Wielemaker, Jan; Diaz, Daniel; Abreu, Salvador; Ciatto, Giovanni (November 2022). "Fifty Years of Prolog and Beyond". Theory and Practice of Logic Programming. 22 (6): 776–858. arXiv:2201.10816. doi:10.1017/S1471068422000102. ISSN 1471-0684.
  53. ^ a b Bonner, A.J. and Kifer, M., 1993, February. Transaction Logic Programming. In ICLP (Vol. 93, pp. 257-279).
  54. ^ Genesereth, M., 2023. Dynamic logic programming. In Prolog: The Next 50 Years (pp. 197-209). Cham: Springer Nature Switzerland.
  55. ^ Kowalski, R., Sadri, F., Calejo, M. and Dávila, J., 2023. Combining logic programming and imperative programming in LPS. In Prolog: The Next 50 Years (pp. 210-223). Cham: Springer Nature Switzerland.
  56. ^ Aho, A.V. and Ullman, J.D., 1979, January. Universality of data retrieval languages. In Proceedings of the 6th ACM SIGACT-SIGPLAN symposium on Principles of programming languages (pp. 110-119).
  57. ^ Maier, D., Tekle, K.T., Kifer, M. and Warren, D.S., 2018. Datalog: concepts, history, and outlook. In Declarative Logic Programming: Theory, Systems, and Applications (pp. 3-100).
  58. ^ Eiter, T., Ianni, G. and Krennwallner, T., 2009. Answer Set Programming: A Primer. In Reasoning Web. Semantic Technologies for Information Systems: 5th International Summer School 2009, Brixen-Bressanone, Italy, August 30-September 4, 2009, Tutorial Lectures (pp. 40-110).
  59. ^ Arias, J.; Carro, M.; Salazar, E.; Marple, K.; Gupta, G. (2018). "Constraint Answer Set Programming without Grounding". Theory and Practice of Logic Programming. 18 (3–4): 337–354. arXiv:1804.11162. doi:10.1017/S1471068418000285. S2CID 13754645.
  60. ^ Denecker, M.; Kakas, A.C. (July 2000). "Special issue: abductive logic programming". Journal of Logic Programming. 44 (1–3): 1–4. doi:10.1016/S0743-1066(99)00078-3.
  61. ^ Eshghi, K., 1988, August. Abductive Planning with Event Calculus. In ICLP/SLP (pp. 562-579).
  62. ^ Eshghi, K. and Kowalski, R.A., 1989, June. Abduction Compared with Negation by Failure. In ICLP (Vol. 89, pp. 234-255).
  63. ^ Nienhuys-Cheng, Shan-hwei; Wolf, Ronald de (1997). Foundations of inductive logic programming. Lecture notes in computer science Lecture notes in artificial intelligence. Berlin Heidelberg: Springer. p. 173. ISBN 978-3-540-62927-6.
  64. ^ Flach, P.A. and Kakas, A.C., 2000. On the relation between abduction and inductive learning. In Abductive Reasoning and Learning (pp. 1-33). Dordrecht: Springer Netherlands.
  65. ^ Cropper, A. and Dumančić, S., 2022. Inductive logic programming at 30: a new introduction. Journal of Artificial Intelligence Research, 74, pp.765-850.
  66. ^ Russell, S., 2019. Human compatible: Artificial intelligence and the problem of control. Penguin.
  67. ^ Shunichi Uchida and Kazuhiro Fuchi. Proceedings of the FGCS Project Evaluation Workshop. Institute for New Generation Computer Technology (ICOT). 1992.
  68. ^ Hewitt, Carl (27 April 2016). "Inconsistency Robustness for Logic Programs". Hal Archives. pp. 21–26. Retrieved 7 November 2016.
  69. ^ Saraswat, V.A. and Rinard, M., 1989, December. Concurrent constraint programming. In Proceedings of the 17th ACM SIGPLAN-SIGACT symposium on Principles of programming languages (pp. 232-245).
  70. ^ Chen, Weidong; Kifer, Michael; Warren, David S. (February 1993). "HiLog: A foundation for higher-order logic programming". Journal of Logic Programming. 15 (3): 187–230. doi:10.1016/0743-1066(93)90039-J.
  71. ^ Miller, D.A. and Nadathur, G., 1986, July. Higher-order logic programming. In International Conference on Logic Programming (pp. 448-462). Berlin, Heidelberg: Springer Berlin Heidelberg.
  72. ^ Andreoli, Jean-Marc (1 June 1992). "Logic Programming with Focusing Proofs in Linear Logic". Journal of Logic and Computation. 2 (3): 297–347. doi:10.1093/logcom/2.3.297.
  73. ^ Hodas, Joshua; Miller, Dale (1994). "Logic Programming in a Fragment of Intuitionistic Linear Logic". Information and Computation. 110 (2): 327–365. doi:10.1006/inco.1994.1036.
  74. ^ Kobayashi, Naoki; Yonezawa, Akinori (1994). Asynchronous communication model based on linear logic. US/Japan Workshop on Parallel Symbolic Computing. pp. 279–294. CiteSeerX 10.1.1.42.8749.
  75. ^ Miller, Dale (30 September 1996). "Forum: A Multiple-Conclusion Specification Logic". Theoretical Computer Science. 165 (1): 201–232. doi:10.1016/0304-3975(96)00045-X.
  76. ^ Kifer, M. and Lausen, G., 1989, June. F-logic: a higher-order language for reasoning about objects, inheritance, and scheme. In Proceedings of the 1989 ACM SIGMOD international conference on Management of data (pp. 134-146).
  77. ^ de Moura, P.J.L., 2003. Design of an Object-Oriented Logic Programming Language (Doctoral dissertation, Universidade da Beira Interior).
  78. ^ Yang, G. and Kifer, M., 2000, July. FLORA: Implementing an efficient DOOD system using a tabling logic engine. In International Conference on Computational Logic (pp. 1078-1093). Berlin, Heidelberg: Springer Berlin Heidelberg.

Sources

[edit]

General introductions

[edit]

Other sources

[edit]

Further reading

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
Logic programming is a in which programs are written as a set of logical statements, typically in the form of Horn clauses, and is performed through mechanisms like resolution and unification to derive answers to queries. This approach contrasts with by focusing on what the program should achieve rather than how to achieve it, allowing the underlying system to handle the via logical . The origins of logic programming trace back to the early , building on foundational work in and . Key developments include J.A. Robinson's resolution principle in 1965, which provided a complete inference rule for , and Robert 's 1971 procedural interpretation of Horn clauses, enabling efficient computation. The paradigm crystallized with the creation of (Programming in Logic) in 1972 by Alain Colmerauer and Philippe Roussel at the University of , initially for French , with contributions from Kowalski at . By the mid-, implementations had evolved to support practical applications, marking the shift from theoretical logic to a viable programming style. At its core, logic programming relies on two primary mechanisms: unification and . Unification is the process of finding substitutions that make two logical terms identical, enabling and variable binding essential for query resolution. implements a strategy, where the system retries alternative clauses or goals upon failure to explore the solution space exhaustively. These features, combined with as failure and the , allow logic programs to model bases declaratively while supporting nondeterministic computation. Prolog remains the flagship language of the paradigm, influencing variants like for deductive database querying and (CLP) for integrating constraints over domains like integers or reals to solve optimization and scheduling problems. Logic programming has found significant applications in , including expert systems for decision support, , and , due to its natural alignment with reasoning and representation. Despite challenges like inefficiency in large-scale search, extensions such as tabling and parallelism continue to enhance its practicality in modern computing.

Overview

Definition and Principles

Logic programming is a in which programs are expressed in the form of logical statements, specifically as a set of facts and rules drawn from predicate logic, and proceeds through automated logical to derive conclusions from these statements. This approach treats the program as a logical theory, where the of is to prove queries against this theory using mechanisms. The core principles of logic programming emphasize declarative specification, where the describes what the desired result is rather than how to achieve it through step-by-step instructions. Programs are typically restricted to subsets of , such as Horn clauses, which consist of a single positive literal (the head) and zero or more positive literals (the body), typically expressed in implication form as Head :- Body, enabling efficient representation of definite knowledge. Computation relies on via resolution, an inference rule that derives new logical statements by unifying and combining existing clauses to refute goals or establish truths. A simple illustrative example involves defining family relationships using facts and rules in pseudo-logic syntax. Facts assert basic relations, such as parent(ann, bob). and parent(bob, charlie)., while rules define derived relations, such as grandparent(X, Y) :- parent(X, Z), parent(Z, Y).. Querying grandparent(ann, charlie). would succeed through resolution, inferring the relationship via the intermediate Z = bob. In contrast to imperative paradigms, which specify explicit sequences of operations and state changes, logic programming inherently supports non-determinism and backtracking, as multiple resolution paths may exist to satisfy a goal, with the system exploring alternatives automatically upon failure. This shifts the focus from control flow to logical consequence, making programs more concise and amenable to verification.

Distinguishing Features

One of the foundational distinguishing features of logic programming is the separation of an into its logic component, which specifies the relations and to be used in problem-solving, and its control component, which determines the strategy for applying that logic. This principle, articulated by in 1979, posits that "Algorithm = Logic + Control," allowing programmers to focus primarily on declarative of what is true, while the underlying manages how to derive solutions through automated . In practice, the logic is expressed via logical clauses, enabling relational queries that the resolves without explicit procedural instructions for search order or iteration. Logic programming's execution model relies on an inference-based approach using over a derivation tree, where the system systematically explores possible proofs by attempting to unify goals with program clauses. Upon failure to satisfy a subgoal, it employs chronological to retract assumptions and retry alternative branches in the order they were originally encountered, ensuring exhaustive exploration until success or complete failure. This strategy contrasts with imperative paradigms by treating computation as proving, where solutions emerge from logical deduction rather than step-by-step commands. A key philosophical distinction lies in its support for non-monotonic reasoning, which allows programs to draw provisional conclusions from incomplete information by assuming defaults that can later be retracted with new evidence. Unlike monotonic logics where added knowledge only expands entailments, logic programming incorporates mechanisms like negation as failure and the , enabling defeasible inferences such as presuming absence unless proven otherwise. While pure logic programming adheres strictly to declarative principles—relying solely on logical clauses and unification for relational definitions—practical implementations often introduce impurities through extra-logical predicates to enhance efficiency or handle side effects. For instance, Prolog's cut primitive (!) commits to the current branch of the search tree, pruning alternatives to avoid redundant exploration and optimize performance in non-deterministic scenarios, though this sacrifices some declarative purity by introducing procedural control.

Historical Development

Early Foundations

The foundations of logic programming trace back to mid-20th-century developments in mathematical logic, particularly first-order predicate logic, which provided the formal framework for expressing knowledge and inferences declaratively. First-order predicate logic, formalized in the early 20th century, enabled the representation of relations, functions, and quantifiers to model complex reasoning, laying the groundwork for computational applications beyond mere calculation. A pivotal contribution was Jacques Herbrand's theorem, published in 1930, which established a constructive characterization of provability in first-order logic by reducing it to propositional logic through Herbrand expansions, thereby highlighting the semi-decidability of validity in this system. Advancements in further bridged and computation during the 1960s. J.A. Robinson's introduction of the in offered a complete, machine-oriented rule for , allowing systematic derivation of conclusions from premises via clausal form, which linked logical deduction directly to algorithmic processes. This , building on Herbrand's ideas, facilitated the of that could handle general problem-solving tasks, influencing early efforts to automate reasoning in computers. One of the earliest systems embodying these ideas was QA4, developed by Cordell Green in 1972 at Stanford Research Institute, which extended resolution-based proving into a programmable language for question-answering and . QA4 allowed users to encode problems as logical axioms and queries, with the automatically generating proofs to derive solutions, serving as a direct precursor to logic-based programming paradigms by demonstrating practical computation through deduction rather than step-by-step instructions. This period also marked a philosophical shift in research from procedural computation—where programs explicitly dictated —to declarative approaches, emphasizing what to compute over how, particularly evident in the and debates around knowledge representation. Influenced by theorem-proving techniques, AI researchers increasingly viewed logic as a means for systems to infer solutions autonomously, promoting and in program design.

Emergence of Prolog

Prolog emerged in the early 1970s as the first practical logic programming language, developed primarily by Alain Colmerauer and Philippe Roussel at the University of Marseille in , with significant theoretical contributions from at the . The collaboration began in 1971 when Kowalski visited Marseille, leading to the integration of his ideas on procedural logic interpretation with Colmerauer's work on formal grammars for . The initial Prolog interpreter, known as Prolog 0, was implemented by Roussel in the fall of 1972 using Algol-W on a Univac 1110 computer, focusing on a rudimentary system for man-machine communication in French. By 1973, a more robust version, Prolog I, was completed in , incorporating features like the cut operator for search control and structure-sharing to avoid redundant term copying during unification. A core innovation of was the seamless integration of unification—a pattern-matching mechanism derived from Colmerauer's earlier Q-systems—with Kowalski's SL-resolution strategy, enabling automatic theorem proving through and without explicit control structures. This combination allowed programmers to declare logical relationships using Horn clauses, leaving the to handle execution, which marked a shift from imperative to . Early efficiency concerns were addressed in implementations like the 1973 version, which used structure-sharing to reduce memory overhead during unification, with developers confident in achieving up to a 25-fold over naive interpreters and laying groundwork for later optimizations. These precursors influenced David H. D. Warren's DEC-10 implementation starting in 1976, which compiled code to machine instructions for comparable performance to systems at the time. Prolog saw early adoption in , where Colmerauer applied it to build a French question-answering system in 1972, comprising 610 clauses for parsing and semantic interpretation, demonstrating its utility for symbolic computation. By the mid-1970s, this extended to , including a restricted Japanese-to-French translator developed by Colmerauer's team, which highlighted Prolog's strength in handling linguistic rules declaratively. The language's practical impact grew internationally in the 1980s through Japan's (FGCS) project, launched in 1982 by the Ministry of International Trade and Industry (MITI), which selected as the foundational language for parallel inference machines aimed at advancing AI and . Standardization efforts culminated in the ISO/IEC 13211-1:1995 specification for the general core of , which formalized syntax, semantics, and built-in predicates based on the widely used / dialect to promote portability across implementations. This standard influenced subsequent language designs by establishing a baseline for features like modules (later detailed in ISO/IEC 13211-2:2000) and negation as failure, ensuring Prolog's enduring role in logic programming.

Post-Prolog Evolution

The (FGCS) project, launched by Japan's Ministry of International Trade and Industry in 1982, aimed to develop advanced computing systems based on logic programming to achieve breakthroughs in , including and parallel processing. The initiative focused on creating non-von Neumann architectures, such as parallel inference machines, to support concurrent logic programming paradigms like those extending . Running until 1992 with a budget exceeding 50 billion yen, the project produced hardware prototypes like the PSI-VLSI chip and multi-processor systems, but it faced challenges in commercialization and did not fully realize its vision of logic-based supercomputing for widespread AI applications. Despite these outcomes, FGCS influenced global research in parallel logic programming and contributed to advancements in knowledge representation. In the 1980s, logic programming saw significant commercialization through robust implementations, marking a shift from academic tools to industrial applications. Prolog, released in 1984, became one of the earliest commercial systems, featuring the (WAM) for efficient execution on hardware like the , and was adopted in AI research and development environments. , initiated in 1987, emerged as a free, open-source alternative optimized for systems and later expanded to support web applications and graphical interfaces, fostering broader accessibility. These systems facilitated integration into enterprise tools, exemplified by Prolog's role in Watson's pipeline, where it enabled efficient and hypothesis generation for question-answering tasks in the DeepQA architecture. Key milestones in the 1990s advanced logic programming beyond standard semantics. Answer set programming (ASP) was formalized by Michael Gelfond and Vladimir Lifschitz, introducing stable model semantics in 1988 to handle non-monotonic reasoning via extended logic programs, with further refinements in 1990 and 1991 that enabled declarative modeling of search problems. Concurrently, (CLP) matured through integration of constraint solvers into logic frameworks, evolving from early systems like III to broader paradigms that combined declarative logic with domain-specific propagation for optimization tasks. These developments, including CLP's merger of logic and constraint solving as surveyed by Jaffar and Maher, expanded applications in combinatorial problem-solving. Post-2010, logic programming has influenced the through hybrids with underlying , enabling rule-based reasoning over ontologies in systems like DLV and dlvhex, which combine answer sets with for scalable knowledge integration. In , post-2010 hybrids such as probabilistic logic programming extensions in ProbLog have merged with logical deduction, supporting applications like parameter learning in Bayesian networks. Open-source ecosystems, centered on and tools like Clingo for ASP, have grown to include libraries for data manipulation and integration with modern languages, promoting collaborative development in AI and .

Core Concepts

Separation of Logic and Control

In logic programming, the foundational principle of separating logic from control, as formulated by , posits that an algorithm consists of a logic component defining the relations to be computed and a control component specifying the for deriving those relations. The logic component captures what the computation should achieve through declarative specifications, such as Horn clauses representing facts and rules, while the control component determines how the computation proceeds, including the order of rule application and search direction. This separation, encapsulated in the equation "Algorithm = Logic + Control," allows the logical content to remain invariant regardless of the inference mechanism employed. This principle enhances the declarativeness of logic programs by decoupling the specification of knowledge from its execution strategy, enabling the same logical relations to be evaluated using varied control regimes without modifying the program's meaning. For instance, can explore solutions level by level to ensure completeness in finding all answers, whereas prioritizes deeper exploration for efficiency in scenarios, yet both operate on identical logical structures. Such flexibility promotes program portability across systems with different underlying engines, as the declarative logic preserves semantic equivalence. To illustrate, consider the logical specification for computing factorials using Horn clauses:

fact(0, 1). fact(succ(X), Y) :- fact(X, Z), mult(succ(X), Z, Y).

fact(0, 1). fact(succ(X), Y) :- fact(X, Z), mult(succ(X), Z, Y).

This defines the factorial relation recursively, where succ denotes the and mult handles multiplication (assumed defined similarly). Under depth-first control, typical in implementations, evaluation proceeds top-down via recursive calls, potentially leading to deep for large inputs. In contrast, bottom-up (iterative) control generates values incrementally from base facts, building up to the query without , as in:

% Bottom-up generation fact(0, 1). % Iteratively apply rules to derive next facts

% Bottom-up generation fact(0, 1). % Iteratively apply rules to derive next facts

Both strategies compute the same relation—e.g., fact(3, 6)—but differ in traversal order, demonstrating how control variations do not alter the logical outcome. Theoretically, this separation aligns with a distinction between procedural semantics, which model the step-by-step execution influenced by control, and , which interpret programs as mathematical functions mapping logical relations to solutions independently of . This framework ensures mathematical correctness, as the declarative reading guarantees that any control yields equivalent results for the intended Herbrand model, provided unification resolves variable bindings during .

Unification and Resolution

In logic programming, unification is the process of finding a substitution that makes two terms identical, serving as the core mechanism for matching patterns in goals and facts or rules. The most general unifier (MGU) of two terms is the substitution that equates them while introducing the fewest possible bindings, preserving generality for further inferences. The computation of the MGU follows the Martelli-Montanari algorithm, which processes a set of between terms through a series of reduction rules until unification succeeds or fails. The algorithm operates nondeterministically on a of equations, applying one of five rules at each step: (1) delete, removing trivial equations t=tt = t; (2) decompose, replacing f(t1,,tn)=f(s1,,sn)f(t_1, \dots, t_n) = f(s_1, \dots, s_n) with t1=s1,,tn=snt_1 = s_1, \dots, t_n = s_n for function symbols ff; (3) conflict, failing if terms have incompatible function symbols or arities; (4) eliminate, selecting an equation v=tv = t where vv is a variable not occurring in tt, substituting vv throughout the remaining equations, and adding the binding to the substitution; (5) occur check, failing if a variable occurs within the term it is to be bound to, preventing infinite structures. This process terminates in linear time and space relative to the input size, ensuring efficiency for practical implementations. Consider unifying the terms parent(X, john) and parent(ann, Y): the algorithm decomposes the equation into X = ann and Y = john, yielding the MGU substitution {X/ann, Y/john} with no further conflicts or occurrences to check. Resolution is the inference rule that drives computation in logic programming by deriving new goals from existing ones using program clauses, typically restricted to linear input resolution for definite (Horn) clauses to ensure efficiency and completeness. In linear input resolution, a resolvent is formed by selecting a goal literal from the current goal set (the "input" from the previous step or initial query) and unifying it with the head of a fresh program clause, replacing the goal with the clause's body under the computed substitution; the process chains linearly, with each new resolvent serving as the parent for the next resolution. For a goal <- G1, G2, ..., Gn and clause H :- B1, ..., Bm, if there is an MGU σ for G1 and H, the resolvent is <- σ(G2, ..., Gn, B1, ..., Bm). This refutation procedure succeeds by deriving the empty clause, proving the initial . Linear input resolution is refutation-complete for Horn clause programs: if a goal is provable (entailed by the program), an SLD-refutation exists. A sketch of the proof proceeds by induction on the height of the minimal SLD-tree for the goal; at the base, atomic goals unify directly with facts; inductively, for a compound goal, the selected subgoal has a refutation by hypothesis, and the remaining subgoals follow by composition under the substitution, ensuring a complete derivation path without gaps in the linear chain. Backchaining implements resolution as a search strategy, constructing proof trees where each node represents a goal set, and children arise from resolving a selected literal against program clauses. The process is depth-first by default in systems like Prolog, exploring one branch to completion before backtracking on failure. Variable scoping is managed through standard variable discipline: upon resolution, variables in the selected clause are renamed to fresh variables distinct from those in the current goal, preventing unintended captures and ensuring existential quantification for query variables and universal for clause variables; the resulting substitution applies only to the local resolvent, composing cumulatively up the proof tree.

Horn Clauses

A Horn clause is a disjunction of literals containing at most one positive literal, providing a restricted yet expressive form within first-order logic. This structure, introduced by Alfred Horn in his analysis of sentences preserved under direct unions of algebras, ensures useful computational properties for automated reasoning and programming. In the context of logic programming, Horn clauses are typically represented in implication form as AB1,,BnA \leftarrow B_1, \dots, B_n, where AA is a single atom (the head) and B1,,BnB_1, \dots, B_n are literals in the body, equivalent to the clausal form ¬B1¬BnA\neg B_1 \lor \dots \lor \neg B_n \lor A. Definite clause programs, the foundation of languages like , restrict bodies to positive atoms, forming a subset known as definite Horn clauses. Syntactic variations of Horn clauses include facts, which are unit clauses with an empty body (AA \leftarrow), asserting the truth of AA unconditionally. Queries, used to initiate computation, take the form of goals with an empty head (B1,,Bn\leftarrow B_1, \dots, B_n), seeking to establish the body literals. To incorporate general first-order logic knowledge into logic programs, formulas are prenex-normalized, skolemized, and converted to clausal normal form; only those resulting clauses qualifying as Horn clauses (at most one positive literal) are retained, preserving the implication-based structure suitable for procedural interpretation. Key properties of Horn clauses underpin their utility in logic programming. The immediate consequence operator TPT_P, which generates new facts from a program PP by applying clause heads when bodies hold, is monotonic: if IJI \subseteq J, then TP(I)TP(J)T_P(I) \subseteq T_P(J), ensuring that program extensions only enlarge the set of derivable facts without invalidating existing ones. This monotonicity guarantees the existence of a least fixed point, corresponding to the least Herbrand model MPM_P, which is the intersection of all Herbrand models of PP and contains precisely the logical consequences of PP over the Herbrand universe. For ground facts—fully instantiated definite Horn clauses without variables—the membership of a ground atom in MPM_P is decidable through iterative application of TPT_P until the fixpoint is reached, as the finite set of ground instances allows termination. A primary limitation of Horn clauses is their inability to express negation directly, as definite forms prohibit negative literals in bodies and clauses lack multiple positive literals to represent disjunctive negations, necessitating separate mechanisms for handling incomplete information. Resolution, the inference rule underlying logic programming execution, processes these clauses by unifying bodies with existing facts to derive new ones, but relies on the syntactic restrictions of Horn form for efficiency.

Semantics and Foundations

Operational Semantics

Operational semantics in logic programming describes the runtime behavior of programs, specifying how queries are processed through inference steps to derive answers or determine failure. This semantics is procedural, focusing on the computation mechanism rather than the logical meaning of the program. For definite logic programs consisting of , the primary inference rule is Selective Linear Definite clause resolution (SLD-resolution), which refines general resolution to support efficient, goal-directed proof search. SLD-resolution operates on a goal, represented as a conjunction of atomic formulas (goals), by repeatedly selecting an atom and resolving it against a program clause, applying a most general unifier (mgu) to produce a resolvent. A derivation is a sequence of such resolution steps: starting from an initial goal G0G_0, it yields goals G1θ1,G2θ2,,GnθnG_1 \theta_1, G_2 \theta_2, \dots, G_n \theta_n, where each θi\theta_i is a substitution composing previous ones, until reaching the empty goal (success) or exhausting options (failure). Derivation trees extend this linearly: each node represents a goal, with branches for alternative clause choices, forming a tree where success leaves provide computed answers via accumulated substitutions, and failure leaves indicate dead ends. For example, in a simple append program with clauses append(nil, Y, Y) and append(cons(X, Xs), Y, cons(X, Zs)) :- append(Xs, Y, Zs), querying append([a], [b], Z) yields a derivation tree with one success branch binding ZZ to [a,b]. Query evaluation begins with an initial goal and proceeds via SLD-resolution until the resolvent is empty, yielding a success substitution, or no further resolution is possible, indicating failure. Substitutions are applied incrementally: each resolution step composes the mgu with the current accumulated substitution, binding variables in the goal and ensuring consistency. The process is depth-first by default in implementations like , selecting the leftmost atom and trying clauses in order. Although operational, this semantics connects to fixpoint theory: the least Herbrand model of the program is the least fixpoint of the immediate consequence operator TPT_P, where TP(I)={AθAB1,,BmP,{B1,,Bm}θI}T_P(I) = \{ A \theta \mid A \leftarrow B_1, \dots, B_m \in P, \{B_1, \dots, B_m\} \theta \subseteq I \} for interpretation II. Computation approximates this iteratively: start with the empty interpretation \emptyset, apply TPT_P successively (TP0=,TPk+1=TP(TPk)T_P^0 = \emptyset, T_P^{k+1} = T_P(T_P^k)), converging monotonically to the least fixpoint lfp(TP)\mathrm{lfp}(T_P), which enumerates all derivable ground atoms. For the append example, iterations build list facts step-by-step until stabilization. Non-determinism arises from multiple applicable clauses or unifiers at choice points, requiring backtracking to explore alternatives upon failure. Implementations maintain a trail of variable bindings for undoing during backtrack and stack choice points to resume from prior branches, enabling exhaustive search of the derivation tree without recomputation. This handles ambiguity, such as in programs with multiple rules for a predicate, ensuring completeness for refutation.

Declarative Semantics

Declarative semantics provides a logical interpretation of logic programs independent of their computational execution, grounding the meaning of a program in classical . For definite logic programs—those consisting solely of without negation—the semantics is defined over the Herbrand universe, which comprises all ground terms constructible from the program's function symbols and constants. An interpretation of a program is a subset of the Herbrand base (the set of all ground atoms over the Herbrand universe), and a model is an interpretation that satisfies all ground instances of the program's clauses. This framework ensures that the program's meaning is the set of all logical consequences derivable from its axioms, emphasizing what the program declares rather than how it computes. For definite programs, which are sets of Horn clauses, there exists a unique minimal model known as the least Herbrand model. This model is the smallest set of ground atoms that satisfies the program and includes all logical consequences; it is constructed iteratively by starting with the empty set and adding atoms justified by the clauses until closure. The uniqueness follows from the monotonicity of the immediate consequence operator associated with the program, ensuring that every definite program has exactly one such minimal model. This least Herbrand model captures the declarative meaning: a ground atom holds if and only if it is in this model. To extend declarative semantics to programs with negation as failure, Clark's completion transforms a logic program into an equivalent set of first-order formulas. For each predicate, the completion replaces the clauses defining it with a single biconditional formula equating the predicate to the disjunction of the bodies of its defining clauses, effectively turning "if-then" implications into "if and only if" equivalences while assuming closed-world reasoning. This completion, comp(P), ensures that the program's models align with its intended declarative reading, where undefined predicates are false, and it provides a foundation for verifying program correctness beyond mere satisfiability. The declarative semantics establishes soundness and completeness with respect to the operational semantics of SLD-resolution for definite programs. Soundness guarantees that any atom derived operationally via SLD-derivations is a logical consequence of the program, hence belongs to the least Herbrand model; this follows from the fact that each resolution step preserves logical entailment from the clauses. Completeness ensures that every logical consequence in the least model can be derived operationally, proven by constructing a successful SLD-tree for any provable ground atom through the program's ground instances. These properties bridge the logical meaning with computation, confirming that operational answers correctly reflect the declarative content. Logic programs under declarative semantics denote binary relations over the Herbrand universe, where equivalence classes of programs share the same least model and thus the same relational meaning. For instance, consider computing the transitive closure of a binary relation q: the program with clauses trans(X,Y) ← q(X,Y). and trans(X,Y) ← trans(X,Z), trans(Z,Y). declares the transitive closure relation, with its least Herbrand model containing exactly the pairs (a,b) where there is a path of length at least one from a to b via q-steps. Different but equivalent formulations, such as linear or recursive variants, yield the same model, illustrating how declarative semantics identifies programs that compute identical relations regardless of procedural differences.

Negation as Failure

In logic programming, negation is primarily handled through the mechanism known as negation as failure (NAF), where a negative literal not(P) succeeds if there is no successful derivation for the positive goal P using the program's rules and facts. This approach embodies a procedural interpretation, particularly in systems like , where the inference engine attempts to prove P and treats failure to do so as evidence for its negation, relying on the closed world assumption that all relevant facts are explicitly stated in the program. Declarativity for NAF was initially formalized via Clark's completion semantics, which transforms a logic program into an equivalent first-order theory by completing each predicate definition to an if-and-only-if biconditional, interpreting negation as classical negation within this completed theory. However, this semantics assumes a monotonic framework and struggles with non-monotonic aspects, such as recursive dependencies involving negation, leading to potential inconsistencies or incompleteness in unstratified programs. For broader non-monotonic reasoning in general logic programs (those allowing negation in rule bodies), provides a foundational declarative interpretation, where models are stable fixpoints obtained by transforming the program and minimizing undefined atoms, though its procedural focus in remains tied to SLDNF resolution. To address limitations in handling loops through negation, well-founded semantics was introduced, defining the meaning of a general logic program via a three-valued logic where atoms can be true, false, or undefined, computed as the least fixed point of a consequence operator over complete partial orders. This semantics, developed by Van Gelder, Ross, and Schlipf, ensures a unique minimal model by iteratively approximating true and false atoms while leaving those in odd loops (cycles of odd length through negation, like mutual exclusion via negation) as undefined, thus avoiding unfounded assumptions in recursive negation. Despite these advances, NAF exhibits key limitations, particularly in distinguishing stratified programs—where predicates can be partitioned into layers with negation only referring to prior layers—from unstratified ones that permit cycles through negation, potentially causing non-termination or ambiguous interpretations in Prolog's execution. In unstratified cases, such as odd loops, well-founded semantics provides a conservative partial model by assigning undefined status, but this incompleteness relative to Clark's completion (where some expected negations may not derive due to floundering or looping) highlights NAF's procedural biases over full declarative completeness. Answer set programming extends these ideas with stable models for non-monotonic reasoning in variants like disjunctive programs.

Relations to Other Paradigms

Comparison with Functional Programming

Logic programming and functional programming represent two distinct declarative paradigms, with logic programming centered on search over relations defined by predicates and rules, while functional programming emphasizes the composition of pure functions that transform inputs into outputs through equational reasoning. In logic programming, programs specify relationships via Horn clauses, and computation involves finding substitutions that satisfy queries through unification and resolution, inherently supporting non-deterministic exploration of solution spaces. In contrast, functional programming constructs programs by defining functions that operate on data structures, relying on beta-reduction and application for evaluation, which ensures referential transparency and deterministic behavior. Regarding expressiveness, logic programming naturally handles disjunction through multiple clauses for a predicate, allowing the representation of alternatives that yield multiple solutions without explicit control structures. achieves similar conditional branching via pattern matching on data constructors, enabling concise definitions for functions like list processing but typically producing a single result per input. For instance, in expressing sorting, employs algorithms like , which recursively partitions lists based on a pivot and composes results deterministically. In logic programming, sorting can be implemented via a permute-and-check approach, generating all permutations non-deterministically and verifying sorted order through relational predicates, leveraging backtracking to explore possibilities. Evaluation strategies further highlight these differences: functional programming uses strict (eager) or lazy evaluation, where arguments are reduced only as needed in lazy systems like , optimizing for demand-driven computation without search overhead. Logic programming, however, employs backtracking via depth-first search in resolution, systematically trying alternatives upon failure, which can lead to exponential exploration but suits constraint satisfaction. Despite these contrasts, both paradigms share traits such as purity—avoiding side effects—and support for higher-order abstractions, with logic programming's inference providing a declarative absence of mutable state akin to functional immutability. This commonality facilitates integrations, such as in functional logic languages that blend narrowing with lazy evaluation.

Comparison with Imperative Programming

Logic programming differs fundamentally from imperative programming in its approach to computation. In logic programming, developers specify relations and facts, allowing the system to infer solutions through automated reasoning, whereas imperative programming requires explicit instructions for state changes and control flow to achieve results. This declarative nature separates the logic of the problem from its execution mechanism, contrasting with imperative paradigms that emphasize sequential commands to manipulate program state. Control flow in imperative programming relies on explicit constructs like loops, conditionals, and jumps to direct execution, enabling programmers to precisely orchestrate the order of operations. In contrast, achieves control implicitly through recursion and backtracking during the resolution process, where the system explores possible derivations without programmer-specified paths. For instance, searching for solutions in a logic program may involve nondeterministic choices resolved by the engine's search strategy, unlike the deterministic steps in imperative code. State management also highlights key distinctions: imperative programs use mutable variables that can be updated repeatedly via assignments, facilitating direct control over data evolution. , however, employs logical variables that start unbound and become instantiated through unification, with bindings persisting but without destructive updates, promoting a more static representation of knowledge. This immutability in logic programs avoids side effects but can complicate modeling dynamic changes compared to imperative's flexible state transitions. Debugging imperative programs benefits from their linear execution trace, allowing tools to step through predictable sequences and inspect state at each point. In logic programming, the nondeterministic search and lack of fixed rule order make debugging more challenging, often requiring exploration of multiple derivation paths to identify discrepancies between expected and actual semantics. Efficiency concerns arise similarly; imperative code optimizes via explicit algorithms, while naive logic implementations may perform exhaustive searches, leading to higher computational costs. For example, graph traversal in imperative programming can use (BFS) with a queue for O(V + E) efficiency, visiting nodes level by level without redundancy. In logic programming, a generate-and-test approach—recursively generating paths and testing for validity—can explore exponentially many invalid paths in cyclic graphs before finding solutions, though optimizations like cycle detection mitigate this. Hybrid systems address these trade-offs by integrating logic's declarative specification with imperative's performance-oriented control, as seen in frameworks like the Logical Programming System (LPS), where reactive rules enable state updates while preserving logical inference. Such combinations allow logic for high-level problem description and imperative elements for efficient execution, improving applicability in domains requiring both reasoning and real-time responsiveness. Advanced implementations, like the Parma system, further bridge efficiency gaps through static analysis, achieving execution speeds comparable to imperative languages for certain logic tasks.

Integration with Object-Oriented Approaches

Logic programming has been integrated with object-oriented (OO) paradigms to create hybrid systems that leverage the declarative reasoning of logic programs alongside the modularity and state management of OO structures. This integration allows for the representation of complex, stateful entities as objects while using logic rules to infer relationships and behaviors dynamically. Key hybrid languages exemplify this approach: Logtalk extends Prolog with OO features such as objects, classes, and protocols, enabling seamless combination of unification-based logic with encapsulation and inheritance. Similarly, Drools, a Java-based rule engine, embeds forward and backward chaining inference within an OO framework, where Java objects serve as facts that logic rules manipulate to enforce business logic. Another example is the combination of Linda's tuple spaces with logic programming in Extended Shared Prolog (ESP), which uses associative matching in shared spaces to coordinate logic-based computations across distributed objects. In these hybrids, encapsulation and inheritance are adapted to logic programming's declarative nature. Encapsulation in Logtalk, for instance, bundles predicates and data within objects, with access controlled via public, protected, or private scopes, preventing unintended side effects while preserving logical purity. Inheritance supports both static class hierarchies and dynamic forms, where logic rules can override or extend behaviors at runtime, contrasting with the fixed class structures in traditional OO languages like ; this dynamic inheritance facilitates flexible reasoning over evolving object states. An illustrative application is agent-based simulation, where OO objects model autonomous agents with internal states, and logic rules define interaction protocols and decision-making, as seen in Logtalk's multi-agent system examples that simulate emergent behaviors in distributed environments. The advantages of this integration include enhanced modularity—OO provides structured organization for large-scale systems, while logic programming enables sophisticated reasoning and query capabilities over object data—leading to more maintainable knowledge-based applications. For example, Drools allows developers to separate declarative rules from imperative OO code, improving scalability in enterprise systems by supporting truth maintenance and conflict resolution. However, challenges arise in balancing declarative purity with OO's statefulness; mutable objects can introduce non-determinism or infinite loops in rule chaining, requiring careful design to ensure consistency and avoid performance bottlenecks in high-volume fact processing. Despite these hurdles, such hybrids promote code reuse and adaptability, particularly in domains like AI and simulation where reasoning over structured entities is paramount.

Knowledge Representation and Reasoning

Representing Knowledge in Logic Programs

In logic programming, knowledge bases are constructed by representing facts as atomic propositions, which are ground instances of predicates that assert the truth of specific statements about the domain. For example, a fact such as parent(john, mary). declares that John is a parent of Mary, serving as a basic unit of declarative knowledge that can be directly queried or used in derivations. These facts form the extensional knowledge base, providing concrete assertions without procedural implications. Rules, on the other hand, encode inferential knowledge as Horn clauses, which are implications of the form head :- body., where the body consists of literals that must hold for the head to be derived. This structure allows for the systematic derivation of new facts from existing ones, enabling the representation of general principles or heuristics in a computable form. Seminal work by Kowalski established this paradigm, emphasizing predicate logic as a vehicle for both problem specification and solution generation in artificial intelligence applications. Ontologies and taxonomies in logic programs are encoded using predicates to define hierarchical relations, such as subclass or instance-of, with transitivity rules facilitating the propagation of properties across levels. For instance, a taxonomy might include facts like animal(dog). and mammal(dog)., along with a rule subclass(X, Y) :- direct_subclass(X, Y). and subclass(X, Z) :- subclass(X, Y), subclass(Y, Z). to ensure transitive inheritance, allowing queries to infer that dogs are animals if mammal is a subclass of animal. This approach leverages the declarative nature of logic programming to model semantic hierarchies without explicit procedural code, supporting inheritance and specialization in knowledge representation. Such representations have been foundational in early for organizing domain concepts, as explored in extensions of logic programming for semantic modeling. While traditional logic programming focuses on crisp, deterministic knowledge, basic handling of uncertainty is introduced through probabilistic extensions that annotate facts and rules with probability distributions, allowing for the representation of incomplete or noisy information. In these systems, a fact might be written as 0.8::parent(john, mary)., indicating an 80% probability, with inference computing marginal probabilities over possible worlds. However, the core emphasis remains on crisp logic, where truth values are binary, and probabilistic variants build upon this foundation for more robust knowledge bases in uncertain domains. This integration preserves the declarative style while extending applicability to real-world scenarios with partial knowledge. Modularity in logic programs is achieved by organizing knowledge into separate modules or namespaces, which encapsulate related facts and rules to manage complexity in large-scale representations. In Prolog implementations, modules function as independent scopes with import/export mechanisms, such as :- module(a, [pred/arity]). declarations, preventing namespace pollution and enabling reusable components. This supports the construction of distributed knowledge bases where modules can be composed dynamically, facilitating maintenance and scalability in ontology-like structures. Early frameworks for such modularity emphasized compositional operators to build programs from subunits while preserving semantic correctness.

Reasoning Mechanisms

Logic programming employs inference mechanisms to derive conclusions from a set of logical rules and facts, primarily through forward and backward chaining, which enable the system to compute answers to queries by exploring the implications of the program. These mechanisms differ in their direction of reasoning: backward chaining proceeds top-down from a goal, while forward chaining builds bottom-up from known facts. Both ensure sound derivations, meaning computed answers logically follow from the program, though their completeness—guaranteeing all entailed facts are derivable—depends on the program's properties, such as being definite ( without negation). Backward chaining is the goal-directed inference strategy central to languages like Prolog, where resolution begins with a query (goal) and recursively reduces it to subgoals until matching facts are found or failure occurs. This top-down approach, formalized as Selective Linear Definite clause (SLD) resolution, uses unification to match goals against rule heads and bodies, enabling efficient depth-first search through the proof tree. In Prolog implementations, efficiency is enhanced by clause indexing, which selects candidate rules based on the goal's functor and arguments before full unification, reducing the search space in large programs. For instance, indexing on the first argument of a predicate avoids scanning irrelevant clauses, a technique pioneered in early Prolog systems to handle practical-scale knowledge bases. Backward chaining's completeness for definite programs ensures that any fact logically entailed by the program can be derived via SLD refutations, provided the search strategy is fair (e.g., breadth-first, though Prolog uses depth-first for efficiency). Forward chaining, in contrast, operates bottom-up by iteratively applying rules to derive new facts from an initial set until no further inferences are possible, often termed materialization. This mechanism is particularly suited to deductive databases, where it computes the least fixpoint of the immediate consequence operator, exhaustively generating all derivable facts. For example, consider a simple program with facts parent(alice, bob). and parent(bob, charlie)., and rule ancestor(X, Y) :- parent(X, Y). ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).; forward chaining would fire the rules iteratively to derive ancestor(alice, charlie). alongside direct ancestors, materializing the full transitive closure. In Datalog systems, optimizations like semi-naive evaluation avoid redundant rule firings by tracking only new facts in each iteration, ensuring scalability for large databases. The completeness of forward chaining mirrors that of backward chaining for definite programs, as the least fixpoint coincides with the least Herbrand model, capturing all entailed positive facts. Meta-interpretation extends these mechanisms by allowing a logic program to reason about another program, treating code as data for tasks like verification or debugging. In this approach, a meta-interpreter written in the same language simulates the execution of an object program, enabling introspection such as tracing resolution steps or detecting infinite loops. For debugging, seminal work introduced algorithmic debugging, where the meta-level program interactively queries the user about subgoals during a failed computation to isolate faulty clauses, assuming an "expected" computation tree. This preserves the soundness of underlying chaining while providing explanatory power, ensuring that diagnoses align with the entailments of the corrected program. Overall, these mechanisms guarantee knowledge completeness in logic programming by linking operational inference to declarative semantics: backward chaining via successful SLD derivations corresponding to proofs, and forward chaining via fixpoint iteration yielding the minimal model, thus ensuring all represented facts are exhaustively entailed under the program's logic.

Connection to Computational Theories of Mind

Logic programming aligns closely with the physical symbol systems hypothesis, which posits that intelligence arises from the manipulation of symbols according to formal rules within a physical system, much like how logic programs operate through declarative rules and inference mechanisms to process symbolic knowledge. This hypothesis, articulated by Newell and Simon, views the mind as a symbol-processing engine capable of generating intelligent behavior via syntactic operations on representations, a process mirrored in logic programming's use of Horn clauses and resolution to derive conclusions from axioms, thereby modeling cognitive processes as computable rule applications. In this framework, logic programs serve as computational models of mental structures, where facts and rules represent beliefs and inference patterns, enabling the simulation of reasoning akin to human symbolic cognition. The roots of this connection trace back to the logicist tradition in artificial intelligence, exemplified by John McCarthy's 1959 proposal for an "advice taker" system, which envisioned a program that could represent and manipulate commonsense knowledge using formal logic to update beliefs and draw inferences, laying foundational ideas for logic programming's role in belief representation. McCarthy's approach emphasized using logical sentences to encode advice and preconditions, allowing the system to reason deductively and adapt its knowledge base, a paradigm directly influencing subsequent developments in logic programming where programs act as dynamic knowledge bases for automated reasoning. This logicist perspective underscores how logic programming facilitates the computational realization of mental states as sets of logical propositions, bridging symbolic AI with theories of mind that treat cognition as rule-governed symbol manipulation. However, logic programming encounters limitations in capturing certain aspects of human cognition, particularly the frame problem, which involves efficiently determining what remains unchanged amid actions or updates in a dynamic world, challenging commonsense reasoning in formal systems. Negation as failure (NAF), a key mechanism in logic programming, addresses this by treating the absence of proof for a statement as evidence of its negation, enabling non-monotonic reasoning that approximates default assumptions and mitigates the frame problem by focusing inference on relevant changes without exhaustive re-specification. This approach, formalized by , allows logic programs to handle incomplete knowledge and exceptions more tractably, aligning with computational theories that view the mind as employing defeasible rules for efficient, context-sensitive cognition. In contemporary views, while logic programming's symbolic foundations emphasize the tractability of explicit reasoning, efforts to integrate it with connectionist models—such as neural networks—seek to combine rule-based precision with subsymbolic learning for more holistic mind models, though the core strength remains in verifiable symbolic inference over opaque pattern recognition. This hybrid perspective acknowledges the hypothesis's enduring influence but highlights logic programming's advantage in domains requiring interpretable, logically sound representations of mental processes.

Variants and Extensions

Constraint Logic Programming

Constraint logic programming (CLP) extends traditional logic programming by incorporating constraint solving over specific domains, enabling declarative modeling of problems that involve relations and restrictions on variables. The foundational CLP(X) scheme, introduced by Joxan Jaffar and Jean-Louis Lassez in 1987, parameterizes the language by a constraint system over a domain X, such as the real numbers (CLP(R)), finite domains (CLP(FD)), or finite sets (CLP(FS)), allowing domain-specific solvers to handle arithmetic, ordering, or set operations efficiently. The operational model of CLP augments standard logic programming execution with a constraint store that accumulates atomic constraints during unification and goal reduction. As constraints are added, propagation techniques simplify the store by inferring implied relations, while entailment checks verify if a new constraint is already satisfied or leads to inconsistency, thereby pruning search paths early in the resolution process. Solutions are obtained by projecting the final store onto query variables using the domain solver, which may yield symbolic representations or enumerated values depending on X. A representative example in CLP(R) involves solving systems of linear equations declaratively, such as finding values for variables satisfying multiple arithmetic relations; for instance, the query ?- X + Y #= 5, 2*X - Y #= 1. resolves to X = 2, Y = 3 through constraint simplification without explicit algorithmic steps. In CLP(FD), scheduling tasks with resource limits can be modeled using constraints like alldifferent for non-overlapping assignments and cumulative for capacity bounds, as demonstrated in applications assigning jobs to machines while respecting durations and precedences to find feasible timetables. CLP provides advantages in handling infinite or continuous domains, such as reals, where pure logic programming struggles with approximation or non-termination, by directly operating in the problem's native mathematical structure for precise solutions. Additionally, it improves efficiency for optimization and search-intensive tasks over vanilla logic programs by exploiting domain-specific propagation and consistency techniques that reduce the solution space exponentially in practice.

Datalog and Deductive Databases

Datalog is a declarative logic-based query language designed for querying and reasoning over relational databases, serving as a foundational formalism for deductive databases. It extends the relational model by incorporating recursive rules while restricting the syntax of to ensure decidability and termination: programs consist of facts, rules, and queries expressed as Horn clauses without function symbols or unsafe variables, allowing only stratified negation to maintain monotonicity under the well-founded semantics. This subset enables efficient bottom-up evaluation on large datasets, distinguishing it from top-down Prolog execution. Deductive databases built on Datalog store extensional database relations (EDB) as facts and define intensional database relations (IDB) via rules, supporting inference of new facts from existing data. The expressive power of Datalog lies in its ability to capture recursive queries that go beyond non-recursive relational algebra, such as computing transitive closures in graphs. For instance, given an EDB relation edge(X, Y) representing directed edges, the transitive closure can be defined by the IDB rule:

tc(X, Y) ← edge(X, Y). tc(X, Y) ← tc(X, Z), edge(Z, Y).

tc(X, Y) ← edge(X, Y). tc(X, Y) ← tc(X, Z), edge(Z, Y).

This computes all reachable pairs (X, Y), enabling applications like path finding in networks. Safety conditions ensure finite results: every variable in a rule's head must appear in a positive body literal, preventing infinite domains from constants or ungrounded recursion. Stratified negation further extends expressiveness for queries involving differences, such as non-recursive predicates, while preserving polynomial-time data complexity for stratified programs. Evaluation in deductive databases typically employs bottom-up fixpoint computation to derive all derivable facts iteratively until convergence. The naive approach repeatedly applies all rules to the current fact set until no new facts emerge, but it recomputes known facts inefficiently. Semi-naive evaluation optimizes this by tracking delta relations of new facts and rewriting rules to join only new facts with old ones or new with new, reducing redundant computations while achieving the same least fixpoint. For query-driven optimization, especially with bound variables, the magic sets transformation rewrites the program to propagate query bindings as auxiliary "magic" predicates, pruning irrelevant derivations and simulating top-down selectivity in a bottom-up framework; this technique, introduced for linear rules, has been generalized to broader classes. Datalog integrates with relational database systems through extensions supporting recursive common table expressions (CTEs), effectively embedding stratified Datalog queries in . In PostgreSQL, the WITH RECURSIVE clause allows Datalog-like recursion for transitive closure or bill-of-materials queries, as standardized in SQL:1999 and later. Oracle employs hierarchical query syntax like CONNECT BY for similar recursive traversals, bridging Datalog's declarative power with 's operational ecosystem. These features enable deductive capabilities in commercial RDBMS, such as integrity constraint checking or virtual view computation over massive datasets, without full Datalog engines.

Answer Set Programming

Answer set programming (ASP) is a declarative programming paradigm that extends with non-monotonic reasoning capabilities, particularly suited for knowledge-intensive search and optimization problems. It allows the representation of problems where solutions correspond to stable models of a logic program, enabling the computation of multiple possible worlds or answer sets that satisfy the program's constraints. Unlike traditional monotonic , ASP handles default negation (negation as failure) in a way that permits retraction of conclusions based on new information, making it ideal for modeling incomplete knowledge and defeasible reasoning. The foundational semantics of ASP is provided by the stable model semantics, introduced by Gelfond and Lifschitz in 1988 for normal logic programs. A stable model of a program P is a set S of atoms such that S is the least model of the Gelfond-Lifschitz reduct P^S, where the reduct is formed by first removing all rules in P that contain a negative literal not a with a ∈ S, and then deleting all remaining negative literals from the surviving rules. This transformation ensures that stable models capture justified conclusions without unfounded assumptions, distinguishing them from other models like the well-founded model. The semantics was later extended in 1991 to include classical negation and disjunctive rules, allowing for more expressive representations of exceptions and alternatives. The syntax of ASP builds on that of logic programs but incorporates constructs for non-determinism and aggregation. A normal rule takes the form a ← b1, …, bn, not c1, …, not cm, where a is the head atom (possibly empty for constraints), positive literals b_i assert facts or conditions, and negative literals not c_j represent negation as failure. Choice rules introduce non-determinism, such as {a1; … ; ak} ← body to generate subsets of atoms in the head or {a} = l..u ← body for bounded choices, enabling the solver to explore multiple possibilities. Aggregates extend expressiveness for counting or summing, e.g., #count{a(X) : cond(X)} > k ← body, which evaluates to true if the number of ground instances satisfying the condition exceeds k; common aggregates include #count, #sum, #min, and #max. These features allow concise encoding of complex constraints without explicit loops or conditionals. ASP solvers compute stable models efficiently using advanced search techniques adapted from solving. The smodels system, developed by Niemelä, was the first full implementation for normal logic programs, employing with linear-time complexity checks for stable models via the reduct. Later, the clasp solver introduced (CDCL), borrowing from SAT solvers to learn nogoods—clauses that prune inconsistent partial assignments—significantly improving on large-scale problems. Clasp supports disjunctive programs and optimization via weak constraints, making it a cornerstone of modern ASP toolchains. ASP excels in applications requiring combinatorial search, such as and configuration. In automated , problems are encoded so that stable models represent valid plans; for instance, (HTN) can be modeled using choice rules for action selection and constraints for precedence, with solvers generating action sequences as answer sets. Configuration tasks, like product assembly or network design, use ASP to find feasible assignments under resource limits, often integrating aggregates for constraints. A representative example is puzzle solving, such as generating minimal Sudoku configurations, where rules define board constraints and choices select cell values, yielding stable models as valid solutions. These use cases highlight ASP's ability to handle NP-hard problems declaratively, with empirical success in domains like and bioinformatics.

Inductive and Abductive Logic Programming

(ILP) is a subfield of that induces programs, typically in the form of Horn clauses, from positive and negative examples combined with background knowledge. Introduced by Stephen Muggleton in 1991, ILP emphasizes the construction of logically sound hypotheses that generalize observed data while compressing the description length of the examples, often guided by principles like minimum description length. This approach allows for learning relational structures, making it suitable for tasks involving complex dependencies beyond flat attribute-value data. A key in ILP involves bottom construction, where for a given positive example, a most specific (bottom ) is generated by applying inverse resolution to the background and the example, defining the search space for . Systems like Progol implement this through inverse entailment, starting from the bottom and performing a general-to-specific search via refinement to find covering hypotheses that maximize coverage of positive examples while avoiding negatives, evaluated by metrics such as predictive accuracy and compression. For instance, Progol has been applied in to learn structure-activity relationships, such as predicting the antibacterial activity of trimethoprim analogues from molecular descriptors, yielding rules that identified novel inhibitors with 100% predictive accuracy on test sets. Abductive logic programming (ALP) extends logic programming to support hypothesis generation, where explanations for observations are computed as sets of abducible atoms—hypotheses from a designated set A—that, when added to the program P, entail the observations while satisfying integrity constraints (IC). Pioneered by David Poole in 1988 through his integration of truth maintenance systems (TMS) for abductive explanations in default reasoning, ALP formalizes abduction as finding minimal sets of assumptions that resolve inconsistencies or explain evidence. The framework (P, A, IC) ensures explanations are consistent, with IC acting as classical logic assertions that prune invalid hypotheses, enabling sound and complete abduction via proof procedures like the IFF procedure. In ALP, integrity constraints enforce domain-specific conditions, such as of hypotheses or causal completeness, by rejecting abductive explanations that violate them during proof construction. This mechanism is crucial for applications like , where observations (e.g., symptoms) are explained by hypothesizing diseases as abducibles; for example, in systems, ALP has been used to generate minimal sets of disorders that account for patient symptoms while respecting constraints like "at most one infectious cause," as demonstrated in early abductive frameworks for fault .

Concurrent and Higher-Order Extensions

Concurrent logic programming extends traditional logic programming paradigms to support parallelism, enabling multiple computations to proceed simultaneously for improved efficiency in problem-solving. In the , languages like Parlog introduced guarded Horn clauses, which augment standard Horn clauses with conditional guards to control commitment and synchronization in parallel execution. These guards allow clauses to be selected nondeterministically only when their conditions are satisfied, facilitating safe concurrent evaluation without race conditions. Parlog, developed by Keith Clark and , emphasized stream communication and deep guard evaluation to exploit both AND-parallelism—where independent conjunctive goals in a query are evaluated concurrently—and OR-parallelism, where alternative clauses are explored in parallel to find solutions. Similarly, Guarded Horn Clauses (GHC), proposed by Kazunori Ueda in 1985, provided a simple framework for parallel logic programming by integrating guards with unification, supporting committed-choice semantics that resolve nondeterminism at runtime while preserving declarative readability. Building on these ideas, concurrent constraint logic programming introduced a model where processes communicate and synchronize through a shared constraint store rather than direct variable binding, enhancing expressiveness for concurrent systems. Vijay Saraswat's cc(X) framework, formalized in his 1989 dissertation, defines a class of languages where proceeds by adding constraints to a global store, with processes blocked until constraints entail necessary information for progress. This store-as-constraint medium enables non-monotonic reasoning and reactive behavior, distinguishing it from earlier concurrent models by treating constraints as first-class communication primitives, which has proven influential in modeling distributed systems and real-time applications. Higher-order extensions to logic programming allow abstraction over predicates and terms, enabling more modular and expressive programs by treating goals and clauses as manipulable objects. Lambda Prolog, developed by Gopalan Nadathur and Dale in the late 1980s, implements higher-order with lambda abstractions, permitting predicates to be passed as arguments, returned as results, or stored in data structures. This supports advanced features like higher-order unification and implication-based reasoning, making it suitable for meta-programming and , while maintaining the declarative style of logic programming. Linear logic programming incorporates resource sensitivity inspired by Jean-Yves Girard's , introduced in , to handle computations where resources like state or data are consumed and not freely duplicated or discarded. This extension addresses limitations in programming for stateful and imperative-like behaviors by enforcing linear use of assumptions, enabling precise modeling of concurrency, mutable state, and resource-bounded reasoning. Systems based on linear logic, such as those extending Lambda Prolog with linear implications, facilitate applications in protocol verification and narrative generation where tracking resource usage is critical.

Applications and Implementations

Practical Uses in AI and Databases

Logic programming has been instrumental in developing expert systems, particularly in , where rule-based reasoning allows for the representation of as logical rules and facts. Successors to the seminal system, such as those for clinical applications in and infectious diseases, leveraged logic programming paradigms to encode heuristic and perform , enabling systems to suggest treatments with explanations grounded in logical derivations. In , logic programming facilitates parsing by defining grammars and semantic structures through definite clause grammars (DCGs) in languages like , which model syntactic and semantic ambiguities as search problems solvable via unification and . This approach has been applied to tasks such as translating queries into logical forms for database access, improving accuracy in handling complex sentence structures compared to purely statistical methods. For planning in , logic programming supports STRIPS-like frameworks by representing actions as logical predicates with preconditions and effects, allowing automated planners to generate sequences of actions through resolution-based search. Systems like those implemented in the DALI language demonstrate how tabled logic programming can efficiently solve planning problems by avoiding redundant computations in state-space exploration. In databases, logic programming enables rule-based querying in the through extensions like SPARQL CONSTRUCT, which treats queries as logical rules to infer new triples from RDF data, supporting recursive reasoning over knowledge graphs. This integration allows for expressive pattern matching and entailment, as seen in SPARQLog, which combines patterns with rule quantification to handle complex deductions without full materialization of intermediate results. For integrity checking, deductive databases use logic programs to define constraints as Horn clauses, verifying data consistency via bottom-up evaluation or proving, which ensures semantic correctness during updates. Beyond AI and databases, logic programming aids verification through , where properties are encoded as logic programs to exhaustively explore system states for safety violations. integrates with model checking to learn invariants from counterexamples, enhancing scalability in hardware and . In automotive configuration, non-monotonic logic programming, such as , models product variants and constraints to generate valid assemblies, optimizing for customer requirements while ensuring manufacturability. Recent integrations with in use logic programming to provide interpretable reasoning layers over neural predictions, combining probabilistic inference with symbolic rules for tasks like visual question answering. A notable case study is Watson's use of Prolog-based logic programming in its pipeline, where logical rules parse queries and infer relationships in unstructured text for question-answering systems. In bioinformatics, logic programming infers signaling pathways by modeling biological interactions as logic rules, enabling the reconstruction of regulatory networks from experimental , as demonstrated in response logic approaches that handle uncertainty in large-scale datasets. These applications underscore the benefits of logic programming's declarative nature, which promotes and explainability in complex, knowledge-intensive domains.

Major Systems and Tools

is a widely used open-source implementation of , featuring extensive libraries for , including an integrated HTTP server framework for building services and web applications. It supports advanced extensions like tabling and interfaces to technologies, making it suitable for research, education, and commercial use. Another prominent variant is SICStus Prolog, a commercial system known for its high-performance engine compliant with ISO standards and built-in support for constraint solving over finite domains and intervals. It emphasizes stability and efficiency, with features for integrating Prolog into larger applications via foreign language interfaces. In the domain of (ASP), the Potassco suite provides a collection of tools centered around Clingo, a state-of-the-art grounder and solver that combines grounding with conflict-driven nogood learning for solving logic programs. Clingo supports multi-shot solving for dynamic problem updates and offers APIs in , Python, and for embedding in other systems. Complementing this, ezASP is a modeling tool designed to simplify ASP development, particularly for educational purposes, by providing a extension with , , and visualization features for answer sets. Other notable systems include XSB, which extends with SLG tabling for efficient handling of recursion and negation as failure under the Well-Founded Semantics (WFS), enabling termination in cases where standard Prolog loops indefinitely. Picat offers a multi-paradigm approach, blending logic programming with functional and imperative elements for scripting and general-purpose tasks, including built-in support for constraint solving and . For constraint logic programming, provides an open-source platform with solver libraries for finite and integer domains, along with interfaces for optimization and integration into hybrid applications. Logic programming ecosystems have grown to include libraries for graphical user interfaces, such as XPCE in , which enables object-oriented GUI development with portable widgets for Windows, , and macOS. Web integration is facilitated by tools like SWISH, a browser-based environment for collaborative editing, execution, and sharing of programs. Cross-language bindings enhance interoperability, with Python libraries like swiplserver allowing seamless querying of from Python scripts via , and the rolog R package enabling queries directly within R for statistical computing.

Challenges and Limitations

One major challenge in logic programming is , stemming from the inherent exponential search space encountered during and resolution processes. In standard implementations like , the evaluation of recursive predicates can lead to repeated computations and exhaustive exploration of possible derivations, resulting in time that grows exponentially with the depth of or the size of the . Techniques such as tabling—memoizing intermediate results to avoid recomputation—and strategies help mitigate this by linearizing certain recursive evaluations, but they impose memory overhead and are ineffective for very large datasets or highly non-deterministic queries where the state space remains prohibitive. Logic programming also faces expressiveness gaps, particularly in handling operations like counting and aggregation without specialized extensions. Core languages such as pure or lack native support for aggregates (e.g., sum, , min), forcing awkward encodings via recursive successor relations or arithmetic that inflate program size and instantiation time quadratically or worse, limiting their utility for database-like queries involving numerical summaries. Additionally, in dynamic domains, the arises: representing the effects of actions requires explicit frame axioms to specify what remains unchanged, leading to an explosion of axioms (proportional to the product of actions and fluents) and complicating non-monotonic reasoning about defaults like . Practical issues further hinder adoption, including the difficulty of debugging non-deterministic code. Unlike deterministic imperative programs, logic programs can yield multiple solutions or backtrack unpredictably, making it challenging to trace execution paths, verify completeness, or isolate semantic errors without specialized tools like declarative debuggers that compare expected versus observed behaviors. Performance comparisons reveal additional gaps; while optimized systems like Aquarius Prolog achieve speeds about five times faster than commercial counterparts on representative benchmarks, they still lag behind imperative languages like C by factors of 10-100 for compute-intensive tasks due to overhead in unification and garbage collection. Looking to future directions, hybrid approaches integrating logic programming with —known as —aim to address these limitations by combining symbolic reasoning's precision with neural networks' ability to handle uncertainty and scale to large, noisy data. Speculative extensions to , such as adapting calculi to encode quantum superpositions and measurements, could potentially parallelize search spaces exponentially, though practical implementations remain nascent as of 2025.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.