Composing Type-Checked Programs (Extended Abstract)
Ulrich Geske Hans-Joachim Goltz National Research Centre for Informatics and Information Technology GMD-FIRST Berlin
Mario Lenz Department of Computer Science Humboldt University of Berlin
[email protected]
fgeske,
[email protected]
January 13, 1995
1 Introduction Debugging of type errors in large programs of Prolog or in procedures with many arguments is often time consuming. For type-free programs of Prolog, types can be generated by abstract interpretation. In this paper we supply a classi cation of types, discuss meaning of the dierent types, show how they can be derived and used for error detection. Modularization of programs have not only advantages for structuring large programs, but especially for the generation of more speci c types, too. For ecient use of these information we propose an extension of the format of the module interface.
2 Debugging and Composing Large Programs When composing large programs from many procedures not only knowledge of mode information of the used procedures is required but also the types of all arguments to a procedure. Due to the speci cation of types for procedures in traditional programming languages many errors can be detected at compile time. Furthermore, a more ecient machine code can be written. Even in some Prolog-Systems the declaration of types is required [1, 6]. The main disadvantages are the increased expenditure when writing programs and a certain risk concerning the chosen types. For type-free Prolog programs type information can be generated by abtract interpretation. Thus sources of errors can be detected before running the program, the programmer is relieved from explicit typing, and suitable types for procedures can be derived (e.g. the smallest type leading to more ecient code). By comparing the type of a call of a procedure and the type of the procedure itself, conclousions concerning the run time behaviour are possible: the call will de nitely fail, the necessary conditions for successfully processing the procedure are satis ed, the call will cause a run time error.
3 Types Dierent kinds of type information have to be distinguished: The research described in this paper was supported by "Bundesminister f ur Forschung und Technologie" under grant 01 IW 206
1
A-type (admissible type): the greatest type of each argument of a procedure such that the
procedure processes without error messages. P-type (procedure type): the greatest type of each argument of a procedure such that the procedure can process succesfully. C-type (call type): the type derived from all calls of the corresponding procedure in a program (characterizing the use of a procedure in a program). The generation of the C-type of a procedure is only meaningful with respect to a closed program (as a modul). I-type (intended type): the type characterizing the intended use of a procedure (determined by the programmer). There are the following relations between the various types of a procedure: A-type P-type C-type and A-type I-type , where the I-type can also be a subset of the P-type or of the C-type. While the I-type cannot be determined from the abstract interpretation, the A-type can be derived by expansion of the P-type, where all unrestricted types are replaced by the greatest type or by a type variable (restricted types are not changed)1 .
4 Abstract Interpretation The objective of abstract interpretation is to ascertain run time properties of a program without actually executing it. By the transition from the domain of discourse to an abstract domain an approximate execution is realized. If type information for Prolog programs is to be derived the abstract domain could consist of the set name, number, list, and fterm. If the interpreter for the abstract interpretation works top-down the risk of a non-terminating execution generally increases. While a bottom-up interpreter avoids this risk, the expenditure of execution is usually increased considerably. The OLDT resolution [5] is a combination pro ting from both methods. In [3] this method is applied to the generation of types by abstract interpretation. However, the derived C-types are not sucient for debugging and composing programs.
5 Modi ed OLDT-Resolution Instead of recording the goals and solutions to determine the C-type of a procedure, we propose to determine the D-type by utilizing the clauses employed for the solution. To ascertain the D-type of a procedure P a call G of P is investigated by a depth- rst meta interpreter using type-uni cation according to the following algorithm: if (G' is a conjunction or disjunction of goals) apply this algorithm conjunctivly or disjunctivly, resp., to the constituents of G' elseif (the type of G' has previously been determined) return elseif (G' is a Bip) use given Bip type information elseif (G' type-uni es with a fact) store G?? as C-type information of G' and return elseif (G' type-uni es with the head of a rule) if this rule has not yet been explored apply this algorithm to the body of the rule endif. Similarly to the OLDT-Resolution, a non-terminating recursion is avoided by looking up a table of previously explored clauses. The type inference requires a user-speci ed partition of the domain into disjoint (basic) types. Using these, derived types can be de ned. Both basic and derived types are de ned by type rules. In case of ambiguous terms a lazy typing is applied, i.e. the determination of the type is delayed. 1 restricted types are marked by the sign `!' ; several built-in predicates of Prolog require restricted types for some arguments (e.g. type '>': number!*number!); procedures using these buil-in's can inherit restricted types
2
For instance, the list [X jY ] in Prolog (see Fig. 1) can be typed as proper list or improper list by type(Y)=empty or type(Y)=number, respectively. An immediate typing as non complete list would preclude the determination of a more exact type. To ascertain the D-type of a procedure P all solutions for a call G of P are determined according to the described algorithm (using disjoint variables as arguments). The solutions are similar to the original call, the arguments being now types or variables. Finally, the various solutions are merged to a single type predicate by searching for the smallest supertype according to the type hierarchy. The smallest type of a variable is the maximal type (e.g. Type). If for two types a common supertype cannot be found, the fusion of both is classi ed as NoTyp indicating a warning to the user.
6 Applications
6.1 Generating type information
We consider the type system given in gure 1 (the chosen denotations of the types are oriented on the standard of Prolog [7]). The terms of Prolog are disjointly subdivided into the types name, number, list, fterm (functional term), and these types again can be structured (such as the type list in this example). There is no type for the set of all terms. But we denote by Type an arbitrary type (i.e. Type is an anonymous type variable). The types and the subset relation between the types can be de ned by clauses in Prolog (examples of such clauses are given in gure 2). Note that, in the given type system, types are subsets of terms with variables (often, type systems for logic programming are based on ground terms, [4]). Prolog-terms
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p
p p
p p
p
p
p
p
p
p
p p
p p
p
p p
p
p
p
p p
p
p
p p
p
p
name
p
number
p
p
p
p
p
p
p p
p p
list
p
p
p
p
p
p
p
p
p
p
type(number,N) :number(N). type(empty, X) :- X == []. type(proper_list, Ey):type(empty, Ey). type(proper_list, [_|T]) :type(proper_list,T). type(partial_list,L) :nonvar(L), L=[_|X], var(X). type(partial_list,L) :nonvar(L), L=[_|X], type(partial_list,X).
p
fterm
#c # c cc ## complete list cons " S \ "" \ S " " \ S " proper list improper list partial list @ @ @ empty
sub_type(empty,proper_list). sub_type(proper_list,complete_list).
Fig. 2 : Some Prolog clauses for the representation of the type system
non empty
Fig.1 : Example of a type system As an example for generating types, we use the following procedures: reverse([],[]). reverse([X|L],M) :reverse(L,N), append(N,[X],M).
append([],K,K). append([Y|N],K,[Y|M]) :append(N,K,M).
Using the call ?- type check(reverse(X,Y),PType,CType) , the abstract interpretation (modi ed OLDT-resolution) generates the following type information: PType = reverse(proper_list,proper_list) CType = append(proper_list,non_empty,non_empty)
3
6.2 Type checking
The modi ed OLDT-Resolution generates type information for each clause. Then type errors can be detected if the types of a procedure are derived from this information: path([X,Y],L,UL) :arch(X,Y,L), L < UL. path([X,Y],L,UpperLimit) :arch(X,Z,L1), path([Z,Y],L2,UL), L is L1 + L2, L < UpperLimit.
revc(nil,X,X). revc([H|T],Acc,L) :revc(T,[H|Acc],L).
arch(berlin,paris,1100). arch(paris,rom,[1437])
?- type_check(revc(_,_,_),PT,CT). PT = revc('NoType'(name,proper_list),Type,Type)) CT = revc('NoType'(proper_list,name),partial_list,partial_list)) ?- type_check(path(_,_,_),PT,CT). PT = path(non_empty,'NoType'(number,non_empty),number!) CT = arch(name,name,'NoType'(number,non_empty))
6.3 Use of modules
An extensive use of the derived type information requires that the program be structured in modules. Otherwise every procedure can be called with arbitrary arguments, either from the top-level or from within other program les | including calls that will cause the procedure to fail (e.g. when used as a test procedure). Thus the C- and P-type determined by abstract interpretation do not make any sense because they only consider successful calls of a procedure. When using modular Prolog, this extension is only possible acording to the exported procedures of a module2.
6.3.1 Inner-module type checking
By calling the type check/3 predicate for all exported procedures of a module it is veri ed that the types of all procedures of a module are compatible. Thereby P-type and C-type information for exported and local procedures, resp., is generated. Type errors are displayed as demonstrated in section 6.2. To derive the C-type of an imported procedure, either the previously generated P-type is used or a P-type with anonymous type variables fro every argument is assumed. The type information of local procedures is only interesting for the developer of that module (determination of incompatible types). Furthermore, they could be used by a compiler to create more ecient code.
6.3.2 Intra-module type checking
Since one cannot assume that exported procedures are only being used for successful calls, the generated P-types of these procedures are expanded to the A-type (see section 3). If both P-type and A-type are exported together with the procedure via the module interface, an automatic assessment of the prospects of the procedure (success, failure, error) is possible. For the reasons of software maintenance we therefore propose to extend the concept of a module for \Standard"-Prolog by (optionally) allowing these declarations in the module interface (besides this, mode-information should be speci able in the module interface, too)3 : :-module(test). :-export([path/3]). :-atype path/3 : Type * Type * number!. :-ptype path/3 : non_empty * number * number!. 2 3
We suppose that procedures which are not exported can only be called within the home module Es wird die Modul-Syntax von [8] verwendet, die um die Angabe von Typinformationen erweitert ist.
4
In typed logic programming languages, as e.g. PROTOS-L ([1]), type declarations in the module interface are obligatory. When composing large programs consisting of several moduls, the test of type compatibility is performed by a call to type check/3 for the top-level procedure. When processing this call the P-type information given in the module interface is used to ascertain the P-type of the top-level procedure and the C-type of all involved procedures. The types are compatible if the C-type of a procedure is enclosed in the corresponding A-type, e.g. for ?- path([berlin,rom],X,5000). and for the unsuccessful call (test predicate) ?- path([],X,5000).
6.3.3 Translation into typed languages
The automatic generation of types supports the transformation of Prolog programs into logic languages with types. In this case, the types of both exported and local procedures have to be declared. The type declarations of the exported procedures are based on the A-types, hence there are two possibilities for type declarations of the local procedures: 1. The type declarations are also based on the A-type (i.e. expansions of types are used). 2. The type declarations are based on the the generated P-types and the exported procedures are extended by corresponding type checking of terms occurring as arguments in local procedures. Advantage : Type information for local procedures remains unchanged. Disadvantage :Modi ed semantic if procedures cause side eects even when failing.
References [1] Beierle, Ch., S. Bottcher, and G. Meyer: Draft of the Logic Programming Language PROTOS-L. IBM IWBS Report 175, 1991. [2] Hanus, M.: Parametric Order-Sorted Types in Logic Programming. In: Proceedings of the TAPSOFT'91, LNCS 494, Springer-Verlag, 1991, 181-200. [3] Kanamori, T. and T. Kawamura, T.: Abstract Interpretation based on OLDT Resolution. The Journal of Logic Programming. Vol5(1993)1&2, 1993. [4] Pfenning, F. (ed.): Types in Logic Programming. MIT Press, 1992. [5] Tamaki, H. and T. Sato: OLD Resolution with Tabulation. In Proc. of the 3. ICLP, London, 1986. [6] PDC-Prolog. Prolog Development Center, H.J.Holst Vej 5A, DK-2605 Broendby, Denmark. [7] Scowen, R. (ed.): ISO/IEC JTC1 SC22 WG17, N110. Prolog. Part1, General core Committee Draft. International Organization for Standardization, National Physical Laboratory, Teddington, Middlesex, England. March 1993. [8] Daessler, K. and C. Pichler (eds.): ISO/IEC JTC1 SC22 WG17, N111. Prolog. Part2, Modules - Working Draft 4.0. International Organization for Standardization, Deutsches Institut fur Normung e.V., National Physical Laboratory, Teddington, Middlesex, England. June 1993.
5