A type classes system for logic programming - Semantic Scholar

16 downloads 18621 Views 173KB Size Report
This paper describes an extension for logic programming. We propose a flexible logi- ... LP by integrating a type classes system similar to that of Haskell. We show how the ...... ment of Computer Science, University of Bristol, 1995. [PFE 92].
A type classes system for logic programming Antonio Fern´andez Dpto. Lenguajes y Ciencias de la Computaci´on, Universidad de M´alaga, 29071 Teatinos, M´alaga, Spain e-mail: [email protected] Fax-number: +34-5-2131397 Abstract This paper describes an extension for logic programming. We propose a flexible logical language that combines overloading and higher-order polymorphism by means of a type classes system. Our proposal consists in classifying the logical predicates, collecting the set of types which share predicates with common application domains in the same type class and particularising more specific applications by the appropriate instantiation of the class. The power and expressiveness of this approach is shown by the declaration of a class of graphs which allows to comprise and solve the kind of well-known benchmarks problems in an expressive and easy way. Besides we propose a natural generalisation of type classes by by means of the concept of constructor class which constitutes a powerful extension of the system. Keywords: Parametric Predicates, Polymorphism, Overloading, Higher-order predicates, Type Constructor.

1

Introduction

The traditional logic programming has experimented a wide range of extensions. The desire of including in logic programming other forms of abstraction such as types ([PFE 92, YAD 91]) and modules ([HIL 93]), has provided a wide field of research over the last years. The advantages are well known. Modularization allows the re-utilisation, encapsulation, importation-exportation, separate compilation and combination of different program codes whereas the traditional purpose of types in logic programming (LP) has been to provide an indication, prior to execution, of errors that a program might contain. The primary error is that happening in the application of a predicate to inappropriate arguments. With a type system, the type of an expression (including predicates) delimits the set of values that this expression can have, protecting the program from syntax errors caused by misuse of the symbols. It is known that the development of types in LP has paralleled that of types in functional programming (FP). (e.g. [MYC 83]) and that LP and FP are closely connected since both paradigms are trying to solve the same problems with techniques that are very similar. Haskell

1

[HUD 92b], one of the most popular FP languages, incorporates a type classes system to constraint the polymorphism and the overloading. The system is different and somewhat richer than most of the type systems of other functional languages [HUD 92a]. In this paper, we extend LP by integrating a type classes system similar to that of Haskell. We show how the concept of type classes system can be adequate for LP and how provides a powerful framework to combine overloading and higher-order polymorphism. The system allows to classify predicates of common application domain in a class and to combine these classes, but at the same time it allows to collect sets of types introducing a particular form of inheritance between classes and types. The types are not only useful to determining program correctness since they play an essential role in the language. Due to the type system, the computation rule becomes a mixture between the standard ”leftmost goal” rule and a particular simulation of the DELAY control facility of G¨ odel [HIL 94]. The delay of a goal is done implicitly by the system and must not be provided explicitly by the programmer. One of the most important innovation that this kind of system introduces in LP is the possibility of parameterising classes (and by default predicates) by type constructors and not only by types. This is possible generalising the concept of type class by means of the idea of class constructor [JON 95]. The main topic of this paper is to show the practical usefulness of including type classes in the logic paradigm and, although there are still several questions to answer and design decisions must be made, it is shown that the type class system add enough expressiveness to be considered as a future extension for any LP language. The second topic consists in showing how this system changes the standard computation rule in LP and how the system can be generalised by the concept of constructor class [JON 95] in an analogous way as in FP. A prototype implementation was developed and allowed us to explore a wide range of applications of type classes and constructor classes. The paper is organised as follows: Section 2 clarifies how our extension differs from others systems, in particular from Haskell and where it fits into LP. Section 2 describes the type classes system for LP and in Section 3 it is shown its power and high expressiveness grouping the kind of benchmark problems in a common class and solving some of them by the adequate instanciations of the class. Section 5 generalises the concept of type class by constructor class. This paper ends with a short conclusion and a discussion of future work.

2

Position of the extension within LP and FP

Recently, some languages, such as Escher [LLO 95], try to combine the characteristics of some of the most important LP and FP languages, such as G¨odel [HIL 94], Haskell [HUD 92b] and λP rolog [NAD 92]. We are far from this idea. It is important to notice that our aim is to adapt to LP a concept proven to be expressive and useful in FP. There are two kinds of type systems studied in LP. One kind are those arised from many sorted first order logic as in G¨ odel. Other are those based in the Churchs simple theory of types as in λP rolog and Escher. Our proposal can be placed in a higher up level to the type system level in LP and can be considered as a final feature of the type systems for LP. The familiarised reader with FP will note that our proposal is a reflection, for LP, of the type classes system of Haskell. We are sure that it is more, because although LP and FP are closely related, they

2

differs in relevant aspects such as the different treatments of types. For instance, in Haskell the type of an expression such as f x y can be inferred if the types of f, x and y are known. However, the type of x can not be inferred directly even if the type of f x y is known. In LP it is possible to infer the type of x, y and/or f known the type of f (x, y) [FER 96b]. Other distinguishing aspect of LP and FP is that (usually) in FP all the arguments of an expression must be ground from left to right. For instance, in Haskell an simple expression such as + x 2 fails. However, in LP the expression +(2, x, y) should return the possible answers for x and y so that 2 + x = y 1 . Both differences allows a different treatment for Haskell’s type classes system and that proposed here for LP. From a non-conventional point of view, our proposal could be view similar to a module system of some FP language (e.g. ML [MYE 93]). It is important to notice that our proposal must be considered in a lower down level to that of a module system since (as in Haskell 1.3) a module might contain declarations of types, classes and instances of these (see next sections). Concluding, our proposal can be viewed as an intermediate system between the type and the module systems of a LP language because a type class contains types and can be contained in a module.

3

The proposed extensions

We propose to extend a typical logic language (the source language) based on the paradigm of purely declarative programming. The extended language is oriented towards exploratory programming and provides the traditional logic syntax, in such way that the suggested extensions can be easily integrated in an wide set of existing logic programming languages.

3.1

Required features in the source language

We need a language supporting parametric polymorphism, very similar to the type systems of modern functional languages such as ML and Haskell. Programmers should declare the types they need and it should be possible to declare parametric and/or recursive types in a similar way as follows data N at = zero | suc(N at). data List a = nil | cons(a, List a). data T ree a = empty | tree(T ree a, a, T ree a)

%%N atural type. %%List type %%binary tree type

N at, List (usually denoted as []), and T ree are called type constructors and zero, suc, nil (also often denoted as []), cons, empty and tree are called data constructors. The programmers should also be able to declare the type signatures of the predicates they define. For example, the typical predicate of appending an element to a list: append/3 :: a → [a] → [a].

%%Declaration

1 Note this can be view as a constraint over x and y and thus from a Constraint Logic Programming point of view.

3

Note the use of the type variable a to model the polymorphism. A type variable ranges over all the types. The declaration above corresponds to append/3 :: ∀a.(a → [a] → [a]). The type variable a can be substituted by an arbitrary type, resulting always a valid type for the predicate append/3. The suggested extensions in this paper can be integrated in a wide set of existing logic languages that incorporates these required characteristics (e.g. Mercury [SOM 96], G¨ odel [HIL 94], or λP rolog [NAD 92]).

3.2

Classifying predicates by means of type classes

The redeclaration is not allowed but it is possible to overload a predicate. The overloading and the higher-order polymorphism is combined by means of the concept of type class. A type class can be viewed as a family of types (really as a family of tuples of types), which has some elements called instances of the class. If C is the name identifying a n-parametric type class, then the declaration C t1 t2 . . . tn (each ti is a type, for i ∈ {1 . . . n}) expresses that the specified tuple of types t1 t2 . . . tn is an instance of the class C. A class introduces a new collection of types (instances of the class) and certain overloaded predicates (called members of the class) which are shared by the types of the class. The instance declaration of a class for a type (or tuple of types) allows the use of the predicates declared in the class (that is, the members). The following example shows the declaration of the class Equal2 declaring the class class Equal (a) where 0= 0/2, 0 \=0 /2 :: a → a. 0 \=0 (X, Y ) : − not(0= 0 (X, Y )). endclass.

%%Equality and inequality declarations %%Specif ication by def ault.

Note that the language must support higher-order predicates (as in the call to not/1 predicate). The 0 \=0 /2 predicate is defined by default depending on the specification of the 0 =0 /2 predicate . The specification of the equality predicate in the instance for a specific type will automatically define the specification for the inequality predicate. A predicate by default can be redefine in an instance. The resolution of a goal is tried with the predicates (re)defined in the instances. If there is no (re)definition in an instance then the goal is solved with the predicate by default defined in the class.

3.3

Contexts: a mechanism to constraint the polymorphism

A context is a condition that a type must hold. For instance, the non-structural equality of lists defined as follows: equal lists([], []). equal lists([X|Xs], [Y |Y s]) : − 0= 0 (X, Y ), equal lists(Xs, Y s). means that two lists are equal if both of them are empty or have all their elements equal (one to one). In this last case, it must exist a predicate (0= 0 /2) declared over the main type of 2

From now on, for simplification, we will work with uniparametric classes.

4

the elements of each of the list (to solve the goal 0= 0 (X, Y )). This condition can be captured by a context in the following way: equal lists :: (Equal a) ⇒ [a] → [a]. The context (Equal a) means that the arbitrary type a must be an instance of the class Equal. This guarantees visibility over the members of the class Equal, and thus over the predicate 0= 0 /2 operating over elements of type a. This expresses the fact that equal lists is not defined on all types, just those for which we know how to compare its elements for equality.

3.4

Instances of a class

The instance of a class for a type introduces the type in the collection of the types of the class and gives visibility to the type over the members of the class. The declaration of a class and its instanciation for a type is another way (together to the contexts) to constraint the polymorphism. For example for each type τ where the equality should be defined, we would have to declare that τ belongs to the class Equal (defined in Subsection 3.2) by providing an implementation of the predicate 0 =0 /2 with type τ → τ . This is done by means of the appropriate instance of the class Equal for the type τ . Instances for lists, naturals and integers are shown in the following example: instance (Equal a) ⇒ Equal ([a]) where %% Equality f or lists 0= 0 ([], []). 0= 0 ([X|Xs], [Y |Y s]) : − 0= 0 (X, Y ), 0 = 0 (Xs, Y s). endinst. instance Equal (N at) where %% Equality f or naturals 0= 0 (zero, zero). 0= 0 (suc(X), suc(Y )) : − 0= 0 (X, Y ). endinst. instance Equal (Int) where 0= 0 (X, X). endinst.

%% Structural Equality f or integers

The context (Equal a) in the instance for lists indicates that the type a can not be an arbitrary type but only an instance of the class Equal, allowing the use of the equality and inequality predicates over the elements of the main type of the lists (that is a). In the example above, we will have defined the equality and inequality over naturals, integers and lists of elements of type Int,N at, [Int], [N at], [[Int]], [[N at]], . . . , etc. 3.4.1

Classes hierarchy. A limited form of inheritance

The contexts in class declarations generate subclasses. A class C1 is subclass of other class C2 (or C2 is superclass of C1 ) if C2 appears in the context of the declaration of C1 . By the definition

5

of context, a class can use the members of its superclasses. This defines a hierarchy of classes (for instance, C1 can be subclass of C2 and C3 , and these of others). A class inherits all of the predicates declared in its superclasses allowing multiple inheritance, since classes may have more than one superclass, and introduces a limited concept of inheritance, since a type can be instance of a class and not of its superclasses. Note that that the mechanism of subclassification of the objects (classes) in the language does not imply an inheritance for the instances, but it introduces a notion of inheritance in visibility (which is got with a context). For instance, the class Order shown below declares the typical order predicates class (Equal a) ⇒ Order (a) where 00 /2, 0=0 /2 :: a → a. 0