SWI-Prolog中面向对象的编程

时间:2015-01-26 16:14:01

标签: oop object module prolog swi-prolog

我在某处读到你可以将模块视为Prolog中的对象。我试图解决这个问题,如果这是一个很好的编码方式。

如果我有两个文件,一个定义一个类狗,另一个使用这个类来制作两个狗对象。

:- module(dog,
      [ create_dog/4,bark/1 ]).

create_dog(Name,Age,Type,Dog):-
   Dog = dog(name(Name),age(Age),type(Type)).

bark(Dog):-
   Dog = dog(name(_Name),age(_Age),type(Type)),
   Type = bassethound,
   woof.
bark(Dog):-
   Dog = dog(name(_Name),age(_Age),type(Type)),
   Type \= bassethound,
   ruff.

woof:-format("woof~n").

ruff:-format("ruff~n").

第二档

use_module(library(dog)).

run:-
   dog:create_dog('fred',5,bassethound,Dog),
   forall(between(1,5,_X),
       dog:bark(Dog)
      ),
   dog:create_dog('fido',6,bloodhound,Dog2),
   dog:bark(Dog2).

这使得一只狗对象狗是一只巴塞特猎犬并使其吠叫5次, 然后,我再做一个狗对象Dog2,这是一个猎犬,并使这也吠。我知道在oop中你有对象有行为和状态。所以我现在根据自己的状态有两个具有不同行为的对象,但目前我将对象的状态存储在Dog变量中,主程序中的代码可以看到它们。有没有办法隐藏对象的状态,即有私有变量? 例如,我可能希望有一种方法可以为每个狗对象存储状态has_barked,如果它在程序中较早出现,则为true,否则为false,然后根据此更改bark/1的行为。 / p>

另外,您将如何处理继承和覆盖方法等? 任何指向读数的指针都欢迎。谢谢。

7 个答案:

答案 0 :(得分:7)

只是Logtalk中示例代码可能重新实现的一个示例。它使用原型来简化,但它仍然说明了一些关键概念,包括继承,默认谓词定义,静态和动态对象以及参数对象。

% a generic dog
:- object(dog).

    :- public([
        create_dog/3, bark/0, name/1, age/1
    ]).

    create_dog(Name, Age, Dog) :-
        self(Type),
        create_object(Dog, [extends(Type)], [], [name(Name),age(Age)]).

    % default definition for all dogs
    bark :-
        write(ruff), nl.

:- end_object.


:- object(bassethound,
    extends(dog)).

    % bark different
    bark :-
        write(woof), nl.

:- end_object.


:- object(bloodhound,
    extends(dog)).

:- end_object.


% support representing dogs as plain database facts using a parametric object
:- object(dog(_Name,_Age,_Type),
    extends(dog)).

    name(Name) :-
        parameter(1, Name).

    age(Age) :-
        parameter(2, Age).

    bark :-
        parameter(3, Type),
        [Type::bark].

:- end_object.


% a couple of (static) dogs as parametric object proxies
dog(fred, 5, bassethound).
dog(fido, 6, bloodhound).


% another static object
:- object(frisbee,
    extends(bloodhound)).

    name(frisbee).
    age(1).

:- end_object.

一些示例查询:

$ swilgt
...
?- {dogs}.
% [ /Users/foo/dogs.lgt loaded ]
% (0 warnings)
true.

?- bassethound::bark.
woof
true.

?- bloodhound::bark.
ruff
true.

?- bassethound::create_dog(boss, 2, Dog).
Dog = o1.

?- o1::bark.
woof
true.

?- {dog(Name, Age, Type)}::bark.
woof
Name = fred,
Age = 5,
Type = bassethound ;
ruff
Name = fido,
Age = 6,
Type = bloodhound.

?- dog(ghost, 78, bloodhound)::(bark, age(Age)).
ruff
Age = 78.

?- forall(between(1,5,_X), {dog(fred,_,_)}::bark).
woof
woof
woof
woof
woof
true.

一些笔记。 ::/2是发送控件构造的消息。目标{Object}::Message使用普通的Prolog数据库简单地证明Object,然后将消息Message发送到结果。目标[Object::Message] 在保留原始发件人的同时将邮件委派给对象

答案 1 :(得分:4)

Logtalk实际上是今天可用的突出面向对象的Prolog。 Paulo将其作为pack提供,因此安装应该非常简单。

模块不适合面向对象。它们更类似于命名空间,但没有嵌套。此外,ISO标准还有点争议。

SWI-Prolog v7引入了dicts,这是一个至少处理语言历史问题的扩展,并按名称提供“字段”,以及“方法”的语法。但是,仍然没有继承...

修改

我在SWI-Prolog中添加了here一个面向对象的小例子。这是我test application关于创建家谱树的演变。

比较genealogy.pl源代码,您可以了解最新版本如何使用模块说明符而不是指令: - multifile,然后可以使用多个树。

你可以看到,调用模块传递给图形构造代码, 并具有可选或强制谓词,由模块限定调用:

make_rank(M, RPs, Rp-P) :-
    findall(G, M:parent_child(P, G), Gs),
    maplist(generated(M, Rp, RPs), Gs).

可选谓词必须调用

...
catch(M:female(P),_,fail) -> C = red
...

请注意,应用程序模块导出的谓词。导出它们,AFAIK,打破面向对象。

==========

另一个,也许是更简单的面向对象的例子,它是模块pqGraphviz_emu,在那里我精心设计了一个简单的系统级对象替换。

我解释一下:pqGraphviz它是一个很小的层 - 用Qt编写 - 通过Graphviz库。 Graphviz - 尽管在C中 - 具有面向对象的接口。实际上,API允许创建相关对象(图形,节点,链接),然后为它们分配属性。我的图层尝试保持API与原始API最相似。例如,Graphviz使用

创建节点
Agnode_t* agnode(Agraph_t*,char*,int);

然后我用C ++接口写了

PREDICATE(agnode, 4) {
    if (Agnode_t* N = agnode(graph(PL_A1), CP(PL_A2), PL_A3))
        return PL_A4 = N;
    return false;
}

我们交换指针,并且我已经设置了Qt元类型工具来处理打字...但由于界面相当低,我通常有一个很小的中间层,它暴露了一个更具应用性的视图,它就是这个中间层从genealogy.pl调用的接口:

make_node(G, Id, Np) :-
    make_node(G, Id, [], Np).
make_node(G, Id, As, Np) :-
    empty(node, N0),
    term_to_atom(Id, IdW),
    N = N0.put(id, IdW),
    alloc_new(N, Np),
    set_attrs(Np, As),
    dladd(G, nodes, Np).

在这个片段中,您可以看到SWI-Prolog v7 dicts的一个示例:

...
N = N0.put(id, IdW),
...

内存分配模式在allocator.pl中处理。

答案 2 :(得分:4)

Prolog模块可以简单地解释为对象(特别是原型)。 Prolog模块可以动态创建,具有可以被视为其身份的名称(因为它在运行会话中必须是唯一的,因为模块命名空间是平坦的),并且可以具有动态状态(使用模块本地的动态谓词)。然而,在大多数系统中,它们提供弱封装,因为您通常可以使用显式限定来调用任何模块谓词(也就是说,至少有一个系统ECLiPSe允许您锁定模块以防止以这种方式破坏封装)。还没有支持将接口与实现分离或者具有相同接口的多个实现(你可以以某种方式破解它,取决于Prolog模块系统,但它并不漂亮)。

Logtalk,如其他答案所述,是Prolog支持大多数系统(包括SWI-Prolog)的高度可移植的面向对象扩展。 Logtalk对象包含Prolog模块,包括概念和实际的观点。 Logtalk编译器支持模块功能的通用核心。你可以使用它,例如在没有模块系统的Prolog实现中编写模块代码。 Logtalk可以将模块编译为对象,并支持对象和模块之间的双向调用。

请注意,逻辑编程中的对象最好被视为代码封装和代码重用机制。就像模块一样。 OO概念可以(并且已经)成功地应用于其他编程范例,包括功能和逻辑。但这并不意味着必然带来命令式/程序性概念。例如,可以解释实例与其之间或原型之间的关系 parent 作为指定代码重用的模式而不是从动态/状态的角度来看(实际上,在从命令式/过程语言派生的OOP语言中,一个实例只不过是一个美化动态数据结构,其规范在其类及其类超类之间分配。)

考虑到您的示例代码,您可以在接近您的配方的Logtalk中轻松地重新编码,但也可以通过其他方式重新编码,其中最有趣的是不使用动态功能。存储状态(如在动态状态下)有时是必要的,甚至可能是特定问题的最佳解决方案(Prolog有理由有动态谓词!)但应谨慎使用,并且只有在真正需要时才使用。使用Logtalk不会改变(或打算改变)。

我建议您查看广泛的Logtalk文档及其众多编程示例。你会在那里找到如何干净地将接口与实现分开,如何使用组合,继承,专门化或覆盖继承的谓词等。

答案 3 :(得分:2)

SWI-Prolog中的PCE系统也是Prolog中OOP的一个选项。它通常与xpce(GUI系统)相关联,但它实际上是一个基于通用类的OO系统。

答案 4 :(得分:1)

查看 logtalk 。它是Prolog的面向对象扩展。

http://logtalk.org/

答案 5 :(得分:1)

如今,SWI prolog已经以一种很好的方式与模块进行交互。请参阅The SWI prolog manual page on dicts,尤其是第5.4.1.1节:用户定义的函数。

这允许您定义看起来与方法完全相同的东西,直到返回值(不寻常但在Prolog中非常有用)。

与其他一些答案中讨论的不同,我个人认为逻辑编程和OOP范例是相互正交的:能够使用OOP模块化构建逻辑代码绝对没有什么坏处。

答案 6 :(得分:0)

像Logtalk中一样,参数化模块比面向对象的编程要多一些。我已经通过一个简单的用户模块“ param”为另一个Prolog系统here重新实现了参数化模块。这是通过ISO模块标准到参数化模块的路由。

在“吠叫狗”示例中,即使不需要面向对象的编程,也可以直接使用ISO模块标准直接完成。但是,让我们展示如何采用Logtalk的指令思想并通过新的模块“ param”来实现它:

文件1:dog.p

:- object(dog).
:- public bark/1.
bark(_) :- write(ruff), nl.
:- end_object.

文件2:bassethound.p

:- object(bassethound).
:- extends(dog).
:- public bark/1.
:- override bark/1.
bark(_) :- write(woof), nl.
:- end_object.

文件3:boundhound.p

:- object(bloodhound).
:- extends(dog).
:- end_object.

由于使用的Prolog系统带有自动加载器,因此我们甚至没有 必须做很多Prolog文本加载。所需要的只是一条路 到源头。即将移植到SWI-Prolog,需要弄清楚变量名的访问和阻塞含义重写技巧。

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- ensure_loaded('<location1>/param.p').
% 1 consults and 0 unloads in 16 ms.
Yes
?- sys_add_path('<location2>/animals/').
Yes

?- bloodhound::bark.
ruff
Yes
?- bassethound::bark.
woof
Yes