Path: utzoo!mnetor!uunet!munnari!mulga!lee From: lee@mulga.oz (Lee Naish) Newsgroups: comp.lang.prolog Subject: Re: modules Message-ID: <2747@mulga.oz> Date: 10 May 88 03:17:09 GMT Reply-To: lee@mulga.UUCP (Lee Naish) Organization: Comp Sci, Melbourne Uni, Australia Lines: 98 Keywords: another module scheme The main uses of a module system are 1) avoiding accidental name clashes, and 2) protecting procedures from being called by anyone. THEOREM: Every module system is either too complicated or not powerful enough. COROLLARY: It is impossible to get more than two people to agree on a module system (the failure of the BSI module committee was therefore inevitable). Prolog module systems have to handle a bit more than module systems of conventional languages because of the ways in which data/code can be converted into other representations and converted back: 1) name/2 converts between atoms (possibly in a particular module) and strings. 2) =.. (and functor/3) converts between atoms and functors with different arities (possibly in a particular module). 3) read/1 and write/1 (etc) convert between terms and text. 4) call/1, clause/2 and assert/1 (etc) "convert" between code and data. We must determine exactly what these operations do (eg, whether they are reversible) and if the language is changed to introduce extra arguments to specify module names. Quite a while back I made a proposal for a module scheme and discussed it with various people at Melbourne uni (the result was inevitable). A version of the scheme was even implemented. One advantage is that it requires no changes to the Prolog system - our implementation was a preprocessor which could be used with any Edinburgh Prolog system. The scheme is atom based (the arity of a functor is irrelevant). The basic idea is that at "compile" time, atoms are mapped to other atoms. To avoid accidental name clashes, module names can be added to atom names. For example, atom 'join' in module 'tree_util' could be mapped to 'tree_util$join'. To implement protection, atoms can be mapped to "random" strings, for example, ' tree_util$join 417309467298353'. Every occurrence of 'join' (with any arity) in module tree_util should be changed. A 'join' predicate or call in another module will (by default) not be renamed, so clashes are avoided. There is no way another module can guess the "random" string an atom is mapped to (the string may depend on when the module was compiled) so protection is achieved (for watertight protection in any system current_predicate etc must be modified also). There are many ways you can define what atoms should be renamed/encrypted. One implementation we have accepts the following syntax: ?- module tree_util : public([list_to_tree, tree_insert]). list_to_tree([], nil). ..... join(T0, T1, T) :- .... ?- endMod : tree_util. This renames every atom which is the name of a procedure defined in the module except list_to_tree and tree_insert (these two atoms are unchanged). There are two options on the preprocessor - one for simply adding module names and one for adding random strings also. Another possible syntax is: :- module tree_util : public [list_to_tree, tree_insert], import [>= from term_comparison], entry [join], local [join_1]. .... :- end_module. This could specify that >= is renamed 'term_comparison$>=', join is renamed 'tree_util$join' and join_1 is renamed ' tree_util$join_1 78435'. One advantage of specifying all atoms which are to be renamed is that only one pass is needed. The whole module system could be implemented by using term_expansion. Nested modules can also be handled very easily. One further feature which may occasionally be useful is to provide a procedure to convert between the original atoms and the renamed versions. To avoid name clashes and compromising protection, this procedure should be renamed. For example, the system could define an is_local procedure (which is local) for a module: ' tree_util$is_local 76835'(join_1, ' tree_util$join_1 78435'). ' tree_util$is_local 76835'(is_local, ' tree_util$is_local 76835'). Lee Naish