Prolog Design Pattern扩展模块谓词

时间:2015-09-21 13:53:12

标签: object design-patterns module prolog

想象一下,我们下面有familytree模块(简单示例):

:- 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 ... ]

我想将这个模块谓词用于不同的" dbs",例如:

:- module(familytree_xyz, []).

male(james).
male(fred).
male(mike).

female(betty).
female(sandra).    

parent(james, fred).
parent(betty, fred).

或者:

:- module(familytree_simpson, []).

male(homer).
male(bart).

female(marge).
female(lisa).

parent(homer, bart).
%[...]

我需要:

  • 运行时上选择db,而不是在编译时选择。
  • 同时使用一个或多个dbs。
  • 扩展db,例如。创建一个“familytree_simpson_extended”数据库模块,其他Simpson系列成员扩展“familytree_simpson”数据库模块(见上例)
  • 符合 swi-prolog

目前,我尝试使用term_expansion/2discontiguous/1multifile/1dynamic/1thread_local/1指令,但是:

  • term_expansion/2似乎只能在编译时使用,
  • discontiguous/1multifile/1,未适应,
  • prolog中的动态dbs被视为“ Evil ”练习,但很多包和库都使用其(penginesbroadcast模块,http lib,例如)。
  • thread_local/1没有详细记录,似乎不常用于prolog源代码(swi-prolog)。

使用动态谓词,我更新以前的代码如下:

%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 ...]  

并且:

%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).

可以使用不同的数据库,如下所示:

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

所以,对不起帖子的长度。问题:

  1. 这个动态谓词解决方案有哪些标准,优点/缺点?这是一个很好的解决方案吗?

  2. prolog在干净/健壮的代码中执行此操作的最佳实践/特定设计模式是什么?**

  3. 关于使用thread_local/1代替dynamic/1并将调用封装到新线程以避免清理数据库有什么用?

3 个答案:

答案 0 :(得分:4)

扩展我的评论,Logtalk解决方案很简单。首先,使用族关系谓词定义根对象:

:- 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.

请注意,查找male/1female/1parent/2的定义始于 self ,即在对象,数据库中,将收到有关家庭关系的问题。从您的示例代码派生的示例将是:

:- 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.

示例查询可以是:

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

您可以根据需要添加任意数量的家庭数据库,同时加载它们,并随意定义它们的特化。例如:

:- 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.

此解决方案可满足您的所有要求。 SWI-Prolog是受支持的Prolog编译器之一。您可以使用其安装程序安装Logtalk。或者,对于SWI-Prolog,您只需键入:

?- pack_install(logtalk).

<强>更新

在对此解决方案的评论中,您询问了如何将数据库注入到族树对象逻辑中。这很容易,但也需要不同的方法。首先将familytree定义为:

:- 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.

请注意,这是另一种选择,我们将male/1female/1parent/2称为本地谓词,但它们也被声明为多文件谓词。现在我们需要在familytree对象中“注入”一个族数据库:

:- 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.

用法示例(假设familytree.lgtsimpsons.lgt个文件):

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

几个示例查询:

?- 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.

答案 1 :(得分:3)

由于源数据库显然在您的用例中起着重要作用,我建议在您的定义中明确指定其专用标识符,以便始终清楚您实际引用的是哪个族源:

db_male(xyz, james).
db_male(simpsons, bart).

db_female(xyz, betty).
db_female(simpsons, marge).

db_parent_of(xyz, james, fred).

因此,您基本上拥有公共和多文件db_male/2db_female/2db_parent_of/3谓词。

自包含模块可以使用自己的源知识库扩展现有定义,在第一个参数中明确说明。这是term_expansion/2等可以帮助您的地方:由于每个模块中的数据库名称相同,您可以编写扩展代码来扩充male/1female/1的模块特定定义使用合适的db参数并将其重写为db_male/2等。请注意,此重写只需在编译时发生。在运行时,您可以提供您选择的任何数据库作为这些谓词的第一个参数。

同样明显的是female/1male/1的全局定义如何:

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

另请注意,我使用parent_of/2等名称来明确哪个参数是什么。

assertz/1可用于在需要时动态扩充每个单独的数据库,再次明确提供名称。但是,对于干净而健壮的代码,我会在编译时尽可能多地做。

答案 2 :(得分:0)

一种替代方法是使用Prolog dicts。它们已由SWI-Prolog引入,并且自1.3.0版以来,也可以在Jekejeke Prolog中获得。如果不需要接收器,则只需使用下划线即可。

文件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.

文件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.

文件familytree.pl:

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

要使家族树在simpson和xyz中也可见,请使用reexport / 1。这允许将消息发送到simpson或xyz,但是仍然会处理来自familytree的方法。这是一个示例运行:

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.

当我们使('。')/ 3呼叫站点意识到后,即将发布的Jekejeke Prolog 1.3.1版本中的gender / 3,parent / 3等产品的出口将消失。但是Jekejeke Prolog的结果是相同的:

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