首页 文章

SWI-Prolog中的面向对象编程

提问于
浏览
12

我在某处读到你可以将模块视为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).

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

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

7 回答

  • 1

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

  • 1

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

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

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

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

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

  • 0

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

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

    SWI-Prolog v7引入了dicts,这个扩展至少处理了该语言的历史问题,并按名称提供'fields',以及'methods'的语法 . 但是,仍然没有继承......

    编辑

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

    比较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中处理 .

  • 7

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

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

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

  • 4

    只是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] 在保留原始发件人的同时将消息委托给对象 .

  • 4

    参数化模块(如Logtalk)比面向对象编程略多一些 . 我通过一个简单的用户模块"param"为另一个Prolog系统here重新实现了参数化模块 . 这种参数化模块的路径是通过ISO模块标准 .

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

    File 1: dog.p

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

    File 2: bassethound.p

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

    File 3: bloodhound.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
    
  • 2

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

    http://logtalk.org/

相关问题