Keywords

These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

1 Context

An obvious requirement for a powerful and flexible programming paradigm seems to be that within the paradigm different types of information can be expressed in suitable languages. However, most traditional programming paradigms and languages do not really have this property. In imperative languages, for example, non-executable background knowledge cannot be described. The consequences become clear when we try to solve a scheduling problem in an imperative language: the background knowledge – the constraints that need to be satisfied by the schedule – gets mixed up with the algorithms. This makes adding new constraints and finding and modifying existing ones cumbersome.

On the other hand, most logic-based declarative programming paradigms lack the capability to express procedures. Typically, they consist of a logic together with one specific type of inference. For example, Prolog uses Horn clause logic and does querying, in Description Logic the studied task is deduction, and Answer Set Programming and Constraint Programming make use of model generation. In such paradigms, whenever we try to perform a task that does not fit the inference mechanism at hand, the declarative aspect of the paradigm is lost. For example, when we try to solve a scheduling problem (which is a typical model-generation problem) in Prolog, we need to represent the schedule as a term, say a list (rather than as a logical structure), and as a result the constraints do not really reside in the logic program, but will have to be expressed by clauses that iterate over a list [5]. Proving that a certain requirement is implied by another, is possible (in theory) for a theorem prover, but not in ASP. Etc.

To overcome these restrictions of existing paradigms, we propose a paradigm in which each component can be expressed in an appropriate language. We distinguish three components: procedures, (non-executable) background knowledge, and concrete data. For the first we need an imperative language, for the second an (expressive) logic, for the third a logical structure (which corresponds to a database). The connection between these components is realized by various reasoning tasks, such as theorem proving, model generation, model checking, model revision, belief revision, constraint propagation, querying, datamining, visualization, etc.

The idea to support multiple forms of inference for the same logic or even for the same theories, was argued in [7]. There it is argued that logic has a more flexible, multifunctional and therefore also more declarative role for problem solving than provided by many declarative programming paradigms, where typically one form of inference is central and theories are written to be used for this form of inference, sometimes even for a specific algorithm implementing this form of inference (such as Prolog resolution). The framework presented here is based on this view and goes beyond it in the sense that it offers a programming environment in which complex tasks can be programmed using multiple forms of inference and processing tools.

2 Overview of the Language and System

To try out the above mentioned ideas in practice, we built a prototype interpreter that supports some basic reasoning tasks and a set of processing tools on high-level data such as vocabularies, structures and theories. In this section we will highlight various decisions in the design of our programming language and interpreter. In the next section we will illustrate the usage of the language with an example. In the remainder of this text we will call our language declimp, which is an aggregation of “declarative” and “imperative”.

2.1 Program Structure

A declimp program typically contains several blocks of code. Each block is either a procedure, a vocabulary (which is a list of sort, predicate and function names), a logic theory over a vocabulary (which describes a piece of background knowledge using the predicate and function names of its vocabulary), or a (possibly partial) structure over a vocabulary (which represents a database over its vocabulary). To bring more structure into a program and to be able to work with multiple files, namespaces and include statements are provided.

Because vocabularies, logic theories and databases are not executable, and a program needs to be executed, control of a declimp program is always in the hands of the procedures. Moreover, when a main procedure is available, the run of the program will start with the execution of this procedure. When there is no main procedure, the user can run commands in an interactive shell, after parsing the program.

In the next sections, we will describe the languages for the respective components in a declimp program.

2.2 Knowledge Representation Language

For representing background knowledge we use an extended version of classical logic. A first advantage of using classical logic as the basis of our knowledge representation language lies in the fact that it is the best-known and most-studied logic. Also, classical logic has the important property that its informal semantics corresponds to its formal semantics. In other words, in classical logic the meaning of expressionsFootnote 1 is intuitively clear. This is an important requirement in the design of a language that is accessible to a wider audience. Furthermore, there are already numerous declarative systems that use a language based on classical logic, or can easily be translated to it. Think of the languages of most theorem provers [8], various Description logics [2], and the language of model generators such as IDP  [10, 22] and Enfragmo  [16].

On the other hand, research in the Knowledge Representation and Reasoning community has clearly shown that pure classical logic is in many ways insufficient. Aggregates and (recursive) definitions are well-known concepts that are common in the background knowledge of many applications, and which can generally not, or not in a concise and intuitively clear manner, be expressed in first-order logic. Therefore, in declimp we use an order-sorted version of first-order logic, extended with inductive definitions [6], aggregates [17], (partial) functions and arithmetic. These extensions make representing knowledge much easier.

2.3 Structures

Structures in declimp are written in a simple language that allows to enumerate all elements that belong to a sort and all tuples that belong to a relation or function. As an alternative to enumerating a relation, it is also possible to specify the relation in a procedural way, namely as all the tuples for which a given procedure returns ‘true’. Furthermore, the interpretation of a function can be specified by a procedure, somewhat similar to “external procedures” in DLV  [3].

As mentioned before, structures in declimp do not necessarily contain complete information, they are not necessarily two-valued. Three-valued structures are useful for representing incomplete information (which might be completed during the run of the program). To enumerate a three-valued relation (or function), two out of three of the following sets must be provided: tuples that certainly belong to the relation, tuples that certainly do not belong to the relation, and tuples for which it is unknown whether they belong to the relation or not. The third set can always be computed from the two given sets.

2.4 Procedures

The imperative programming language in our prototype system is Lua  [11]. The main reason for this choice is the fact that Lua is a lightweight scripting language and also because it has a good C++ API [12]. This facilitates on the one hand the compilation of programs written in declimp and, on the other hand, the integration with the components of our declimp interpreter, which is written in C++. When we do not take those reasons into account, any other imperative language is candidate.

In procedures, various reasoning methods on theories and structures can be called. Currently, the most important tasks supported by the declimp-interpreter are the following:

  • Finite model expansion: Given a three-valued structure \(S\) and a theory \(T\), find a completion of \(S\) to a two-valued structure that satisfies \(T\). This is essentially a generalization of the reasoning task performed by ASP solvers, constraint programming systems, Alloy analyzers, etc. It is suitable for problems such as scheduling, planning and diagnosis. In our declimp interpreter, model expansion is implemented by calls to the IDP system [22], which consists of the grounder GidL  [23] and solver MinisatID  [13].

  • Finite model checking: Check whether a given two-valued structure is a model of a theory. This is an instance of model expansion and is implemented as such.

  • Constraint propagation: Deduce facts that must hold in all models of a given theory which complete a given three-valued structure. This is a useful mechanism in configuration systems [20] and for query answering in incomplete databases [4]. The propagation algorithm we implemented is described in [21].

  • Querying: Given an formula \(\varphi \) and a two-valued structure \(S\), find all substitutions for free variables of \(\varphi \) that make \(\varphi \) true in \(S\). The implementation of this mechanism makes use of Binary Decision Diagrams as described in [23].

  • Theorem proving: Given two theories \(T_1\) and \(T_2\), check whether \(T_1 \models T_2\). This is implemented by calling a theorem prover provided by the user. In principle, any theorem prover that accepts TPTP  [18] can be used.

  • Visualization: Show a visual representation of a given structure. We implement this by calling IDPDraw, a tool for visualizing finite structures in which visual output is specified declaratively by definitions in our knowledge representation language or in ASP.

The values returned by the reasoning methods can be used as input for other reasoning methods and Lua-statements. We will illustrate this with an example in the next section.

3 Programming in DECLIMP

Say we want to write an application that allows players to solve sudoku puzzles. Such an application should be able to perform tasks such as generating puzzles, showing puzzles on the screen, checking whether solutions (player’s choices) satisfy the sudoku rules, giving hints to the player, etc. In this application the different components we described before can clearly be distinguished: (1) the background knowledge consists of a logic theory containing the well-known sudoku constraints;

$$\begin{aligned} \begin{array}{l} \forall r \forall n \exists ! c : Sudoku(r,c) = n\\ \forall c \forall n \exists ! r : Sudoku(r,c) = n\\ \forall b \forall n \exists ! r \exists ! c : InBlock(b,r,c) \wedge Sudoku(r,c) = n\\ \forall b \forall r \forall c : InBlock(b,r,c) \Leftrightarrow \\ \qquad \quad b = ((r-1) - ((r-1) \mathrm{mod}~{3})) + ((c-1) - ((c-1) \mathrm{mod}~{3}))/3 + 1\\ \end{array} \end{aligned}$$

(2) the data is stored in logical structures representing puzzles, and (partial and complete) solutions; and (3) the tasks we want it to perform, can be implemented using well-known inference methods. For example, “given a partial solution, complete the solution” is a typical model expansion task.

Below we show (a part of) a declimp program. The code shows the use of an include statement and a namespace, and the declaration of a vocabulary sudokuVoc and a theory sudokuTheory, where the latter is simply an ASCII version of the theory shown above. Also note the main procedure at the bottom, which will automatically be called when the program is passed to the interpreter.

figure a

Let us have a closer look at procedure createSudoku for creating sudoku puzzles. First it initializes an empty puzzle by instantiating a new logical structure. This is done by calling a procedure makeEmptyGrid which instantiates a structure with data about a generic grid of a certain size, and then adding domains for numbers and blocks particular for sudoku grids.

The second part of the procedure adds numbers to the grid until there is only one solution left for the puzzle. This is realized by performing model expansion (by calling modelExpand) to find two models of the theory that extend the given partially filled in puzzle. When two models are found, the algorithm selects a number that is unique for the first solution (that is, the number at the same position in the second solution is different) and is not yet present in the puzzle. When such an entry is found, it is added to the puzzle by making the tuple \(\{ \mathtt{{row,col,num}} \}\) true in the interpretation of the function Sudoku(Row,Col):Num. Next, the procedure asks for two new models, and the process starts over. When only one model is found, the iteration stops, and procedure printSudoku is called to show the result on the screen using the visualization tool mentioned in the previous section.

4 Related Work

There have been many proposals in the literature to combine procedural and declarative languages. A frequently occuring combination is that of a procedural language in which a program can post constraints expressed in an (often ad-hoc) declarative constraint language, while other primitives allow to call the constraint-solving process on the constraint store, express heuristics or call other processes, for example to edit or visualize output. Examples of systems with such languages are CPLEX  [1], Mozart  [19] and Comet  [15]. These systems differ from declimp in the sense that they offer only one kind of inference, namely constraint solving. A similar remark can be made about CLP and Prolog systems with support for constraint propagation. There the “procedural language” is the Prolog language under its procedural semantics. In our system high-level concepts such as vocabularies, theories and structures are treated as first-class citizens that can be operated upon by arbitrary inference and processing tools, which offers more flexibility.

For another group of systems, control over execution of programs is in hands of one inference mechanism – or at least that inference is the main mechanism – and an integrated procedural language then allows users to stear some aspects of the inference mechanism, or for example format input and output, but do not allow to take over control. Examples of such systems are clingo  [9] and Zinc  [14]. The procedural languages in these systems have a more limited task then the one in declimp. In declimp the procedures are in control during execution, not just one of the inference mechanisms.

5 Conclusion

We have presented a knowledge-based programming environment, providing a declarative language for expressing background knowledge, an imperative programming language for writing procedures, and logical structures for expressing concrete data. The system also provides state-of-the-art inference tools for performing various reasoning tasks.

We believe that a programming environment like the one proposed here overcomes some of the limitations of “single-programming-style” paradigms, by allowing a programmer to express the different types of information in software applications in appropriate languages. Making this explicit distinction between different types of information will increase readability, maintainability and reusability of programming code.

The prototype presented here has evolved into a new version of the IDP system. It can be obtained from http://dtai.cs.kuleuven.be/krr/software.