Prolog Design Pattern for extending module predicates

Imagine we have a familytree module below (a simple example):

 :- module(familytree, [ father/2, mother/2, %[...] ]). father(X,Y) :- male(X),parent(X,Y). father(unknown, _) :- male(unknown). mother(X,Y) :- female(X),parent(X,Y). mother(unknown, _) :- female(unknown). sister(X,Y) :- female(X),parent(Z,X),parent(Z,Y), X \= Y. %[... other relation predicates ... ] 

I want to use this module for predicates with different "dbs", for example:

 :- module(familytree_xyz, []). male(james). male(fred). male(mike). female(betty). female(sandra). parent(james, fred). parent(betty, fred). 

Or:

 :- module(familytree_simpson, []). male(homer). male(bart). female(marge). female(lisa). parent(homer, bart). %[...] 

I need:

  • choose db at runtime rather than compiling.
  • use one or more dbs at the same time.
  • for db extension, for example. create a module "familytree_simpson_extended" db with other members of the Simpson family, expanding the db module "familytree_simpson" (see example above)
  • Must be compatible with swi-prolog .

I am currently trying to play with the term_expansion/2 , discontiguous/1 , multifile/1 , dynamic/1 and thread_local/1 , but:

  • term_expansion/2 seems suitable for use at compile time,
  • discontiguous/1 , multifile/1 , not adapted,
  • dynamic dbs in the prologue are considered as an "evil" practice, but for many packages and libraries for example they use their own module ( pengines , broadcast , http lib).
  • thread_local/1 not very documented and does not seem to be used often in prolog source code (swi-prolog).

When playing with a dynamic predicate, I update the previous code as follows:

 %familytree.pl :- module(familytree, [ familytree_cleanup_db/0, familytree_use_db/1, %[... previous declarations ...] ]). dynamic male/1, female/1, parent/2. familytree_cleanup_db :- retractall(male/1), retractall(female/1), retractall(parent/2). familytree_use_db(ModuleName) :- assert(male(X) :- ModuleName:male(X)), assert(female(X) :- ModuleName:female(X)), assert(parent(X,Y) :- ModuleName:parent(X,Y)). %[... previous predicates ...] 

AND:

 %main.pl % use familytree tool predicates :- use_module(familytree). %load all familytree dbs at compile time. :- use_module(familytree_xyz). :- use_module(familytree_simpson). :- use_module(familytree_simpson_extended). main_xyz:- familytree_cleanup_db, familytree_use_db(familytree_xyz), process. main_simpson_all :- familytree_cleanup_db, familytree_use_db(familytree_simpson), familytree_use_db(familytree_simpson_extended), process. process :- findall(X, father(X,_), Xs), write(Xs). 

And it is normal to use with different db as follows:

 ?- main_simpson_all. [homer,homer,abraham] true. ?- main_xyz. [james] true. 

So sorry for the length of the post. Questions:

  • What are the criteria, pros and cons to consider when using this solution of dynamic predicates? this is a good decision?

  • What is the best practical / concrete design pattern for a prolog for this in clean / robust code? **

  • How to use thread_local/1 instead of dynamic/1 and encapsulate the call in a new thread to avoid db clearing?

+5
source share
4 answers

Expanding my comment, Logtalk's solution is simple. First, define a root object with a family relationship predicate:

 :- object(familytree). :- public([ father/2, mother/2, sister/2, brother/2 ]). :- public([ parent/2, male/1, female/1 ]). father(Father, Child) :- ::male(Father), ::parent(Father, Child). mother(Mother, Child) :- ::female(Mother), ::parent(Mother, Child). sister(Sister, Child) :- ::female(Sister), ::parent(Parent, Sister), ::parent(Parent, Child), Sister \== Child. brother(Brother, Child) :- ::male(Brother), ::parent(Parent, Brother), ::parent(Parent, Child), Brother \== Child. :- end_object. 

Note that the search for the definitions of male/1 , female/1 and parent/2 starts on its own, i.e. in an object, a database that will receive requests for family relationships. An example derived from your sample code:

 :- object(simpsons, extends(familytree)). male(homer). male(bart). female(marge). female(lisa). parent(homer, bart). parent(homer, lisa). parent(marge, bart). parent(marge, lisa). :- end_object. 

An example request could be:

 ?- simpsons::parent(homer, Child). Child = bart ; Child = lisa. 

You can use as many family databases as you want, load them at the same time, and define their specializations as you wish. For instance:

 :- object(simpsons_extended, extends(simpsons)). male(Male) :- ^^male(Male). male(abe). male(herb). female(Male) :- ^^female(Male). female(gaby). female(mona). parent(Parent, Child) :- ^^parent(Parent, Child). parent(abe, homer). parent(abe, herb). parent(gaby, herb). parent(mona, homer). :- end_object. 

This solution meets all your requirements. SWI-Prolog is one of the supported Prolog compilers. You can install Logtalk using its installers. Alternatively, for SWI-Prolog you can simply enter:

 ?- pack_install(logtalk). 

Update

In your commentary on this decision, you asked about entering a database into the logic of family tree objects. It is easy, but it also requires a different approach. First define familytree as:

 :- object(familytree). :- public([ father/2, mother/2, sister/2, brother/2 ]). :- public([ parent/2, male/1, female/1 ]). :- multifile([ parent/2, male/1, female/1 ]). father(Father, Child) :- male(Father), parent(Father, Child). mother(Mother, Child) :- female(Mother), parent(Mother, Child). sister(Sister, Child) :- female(Sister), parent(Parent, Sister), parent(Parent, Child), Sister \== Child. brother(Brother, Child) :- male(Brother), parent(Parent, Brother), parent(Parent, Child), Brother \== Child. :- end_object. 

Note that this is an alternative, we call male/1 , female/1 and parent/2 as local predicates, but they are also declared as set predicates. Now we need to โ€œenterโ€ the family database into the familytree object:

 :- category(simpsons). :- multifile([ familytree::male/1, familytree::female/1, familytree::parent/2 ]). familytree::male(homer). familytree::male(bart). familytree::female(marge). familytree::female(lisa). familytree::parent(homer, bart). familytree::parent(homer, lisa). familytree::parent(homer, maggie). familytree::parent(marge, bart). familytree::parent(marge, lisa). familytree::parent(marge, maggie). :- end_category. 

Usage example (assuming familytree.lgt and simpsons.lgt files):

 ?- {familytree, simpsons}. ... yes 

A few sample queries:

 ?- familytree::parent(homer, Child). Child = bart ; Child = lisa ; Child = maggie. ?- familytree::male(Male). Male = homer ; Male = bart. ?- familytree::father(Father, Child). Father = homer, Child = bart ; Father = homer, Child = lisa ; Father = homer, Child = maggie ; false. 
+4
source

Since the source database obviously plays an important role in your use case, I suggest making your highlighted identifier explicit in your definitions, so itโ€™s always clear which family source you are actually referring to:

 db_male(xyz, james). db_male(simpsons, bart). db_female(xyz, betty). db_female(simpsons, marge). db_parent_of(xyz, james, fred). 

So, you basically have the predicates public and multifile db_male/2 , db_female/2 , db_parent_of/3 .

Stand-alone modules can extend existing definitions to their own source knowledge bases made explicit in the first argument. Here term_expansion/2 etc. They can help you: since the database name is the same in each individual module, you can write extension code that complements the module-specific definitions male/1 , female/1 , etc. With suitable db and rewrite this to db_male/2 , etc. Please note that this rewriting is only necessary at compile time. At run time, you can specify any DB that you select as the first argument to these predicates.

It is also obvious how the global definitions of female/1 , male/1 can look like this:

 male(M) :- db_male(_, M). 

Also note that I'm using names like parent_of/2 to figure out which argument is what.

assertz/1 can be used to dynamically grow each individual database as needed, again explicitly specifying a name. However, for clean and reliable code, I would do as much as possible at compile time.

+3
source

Probably best kept secret in Prolog or language history. You can easily turn your modular system into a class system, as was done in (1). Our class system consists of the following two operators: these operators are defined here :

 (::-)/2: Late binding rule. (::)/2: Late binding sending. 

So, the familytree base class is then read as follows, the full source is found here , note that you need to increase the arity of the exported elements

  : - module (familytree, [father / 3, mother / 3, sister / 3]).
 : - reexport (classfun).

 father (X, Y) :: - male (X), parent (X, Y).
 Etc .. 

Then the subclass of simpson class is defined as follows: the full source is found here :

  : - module (simpson, [male / 2, female / 2, parent / 3]).
 : - reexport (familytree).

 male (homer) :: - true.
 Etc .. 

A subclass of xyz is defined similarly; the full source is found here . After consulting with simpson and xyz, for example, in SWI Prolog, you can request the following:

 ?- simpson::father(X,Y). X = homer, Y = bart ; false. ?- xyz::father(X,Y). X = james, Y = fred ; false. 

Our definition of the operator (::)/2 :) (::)/2 here is pioneered by systems such as Prolog ++, object orientation SICStus or Logtalk, but specifically uses modern modular systems, for example, in SWI-Prolog or Jekejeke Prolog.

For a more complete implementation, you need to add additional rewrite rules to the add_body_send / 3 predicate, currently it only accepts Horn sentences, but can also be extended to the logic that is currently found in Prolog systems.

Bye

(1)
To start, see also section 6.6.
Convert object-oriented programs to:
Checking serial and parallel programs
Authors: Apt, Krzysztof, de Boer, Frank S., Alderog, Ernst-Rรผdiger
Texts in Computer Science, 2009, Springer Verlag
http://www.springer.com/us/book/9781848827448

+2
source

An alternative is to use the dictations of the Prologue . They were introduced by SWI-Prolog, and since version 1.3.0 they are also available in the Jekejeke Prolog. If the receiver is not needed, you can simply use underscore.

File simpson.pl:

 :- module(simpson, [gender/3, parent/3]). :- reexport(familytree). _.gender(homer) := male. _.gender(bart) := male. _.gender(marge) := female. _.gender(lisa) := female. _.parent(homer) := bart. _.parent(homer) := lisa. _.parent(homer) := maggie. _.parent(marge) := bart. _.parent(marge) := lisa. _.parent(marge) := maggie. 

File xyz.pl:

 :- module(xyz, [gender/3, parent/3]). :- reexport(familytree). _.gender(james) := male. _.gender(fred) := male. _.gender(mike) := male. _.gender(betty) := female. _.gender(sandra) := female. _.parent(james) := fred. _.parent(betty) := fred. 

File familytree.pl:

 :- module(familytree, [father/3]). M.father(X) := Y :- male = M.gender(X), Y = M.parent(X). 

To make the family tree visible also in Simpson and XYZ, use reexport / 1. This allows you to send messages to simpson or xyz, but, nevertheless, the method from the family tree will be processed. Here is an example run:

 Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.19) SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software. ?- Y = simpson{}.father(X). Y = bart, X = homer ; Y = lisa, X = homer ; Y = maggie, X = homer ; false. ?- Y = xyz{}.father(X). Y = fred, X = james ; false. 

Export gender / 3, parent / 3, etc. It will disappear with the release of 1.3.1 Jekejeke Prolog when we find out ('.') / 3 the site of the call. But the result for Jekejeke Prolog is the same:

 Jekejeke Prolog 3, Runtime Library 1.3.0 (c) 1985-2018, XLOG Technologies GmbH, Switzerland ?- Y = simpson{}.father(X). Y = bart, X = homer ; Y = lisa, X = homer ; Y = maggie, X = homer ; No ?- Y = xyz{}.father(X). Y = fred, X = james ; No 
0
source

Source: https://habr.com/ru/post/1231954/


All Articles