SWI-Prolog 中的面向对象编程
Objected Oriented Programming in SWI-Prolog
我在某处读到,您可以将模块视为 Prolog 中的对象。我正在努力解决这个问题,如果它是一种很好的编码方式。
如果我有两个文件,一个定义 class 狗,另一个定义 class 来制作两个狗对象。
:- 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
更改行为关于这个。
另外,您将如何处理继承和重写方法等?
欢迎任何指向读数的指针。谢谢。
看看logtalk。它是 Prolog 的一种面向对象的扩展。
Logtalk 实际上是当今可用的突出的面向对象的 Prolog。 Paulo 将其作为 pack 提供,因此安装应该非常简单。
模块并不真正适合面向对象。它们更类似于名称空间,但没有嵌套。另外,ISO标准有点争议。
SWI-Prolog v7 引入了 dicts,一个扩展,至少可以处理该语言的历史问题,并按名称提供 'fields',以及 'methods' 的语法。但是,仍然没有继承...
编辑
我添加了 here a small example of object orientation in SWI-Prolog. It's an evolution of my 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 与原始图层最相似。例如,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 指令的示例:
...
N = N0.put(id, IdW),
...
内存分配方案在 allocator.pl 中处理。
Prolog 模块可以简单地解释为对象(具体来说,作为原型)。 Prolog 模块可以动态创建,有一个可以被视为它们身份的名称(因为它在 运行 会话中必须是唯一的,因为模块命名空间是平面的),并且可以有动态状态(使用局部的动态谓词到模块)。然而,在大多数系统中,它们提供弱封装,因为您通常可以使用显式限定调用任何模块谓词(也就是说,至少有一个系统 ECLiPSe 允许您锁定模块以防止以这种方式破坏封装)。也不支持将接口与实现分离或具有同一接口的多个实现(您可以以某种方式破解它,具体取决于 Prolog 模块系统,但它并不漂亮)。
如其他答案中所述,Logtalk 是 Prolog 的高度可移植的面向对象的扩展,支持大多数系统,包括 SWI-Prolog。 Logtalk 对象包含 Prolog 模块,从概念和实践的角度来看。 Logtalk 编译器支持模块功能的通用核心。您可以使用它,例如在没有模块系统的情况下在 Prolog 实现中编写模块代码。 Logtalk可以将模块编译为对象,支持对象与模块的双向调用
请注意,逻辑编程中的对象最好被视为一种代码封装和代码重用机制。就像模块一样。 OO 概念可以(并且已经)成功应用于其他编程范例,包括功能和逻辑。但这并不意味着一定要带来 imperative/procedural 概念。例如,实例与其class或原型之间的关系因为它的 parent 可以解释为指定 代码重用模式 而不是从 dynamic/state 的角度来看(事实上,在源自 imperative/procedural 语言的 OOP 语言中,一个实例只不过是一个美化的动态数据结构,其规范分布在它的 class 和它的 class superclass 之间es).
考虑到您的示例代码,您可以在接近您的公式的 Logtalk 中轻松地重新编码它,但也可以使用其他方式,其中最有趣的方式不使用动态功能。存储状态(如在动态状态中)有时是必要的,甚至可能是特定问题的最佳解决方案(Prolog 有动态谓词是有原因的!)但应该谨慎使用,并且只有在真正需要时才使用。使用 Logtalk 不会改变(也不打算改变)这一点。
我建议您查看内容广泛的 Logtalk 文档及其大量的编程示例。在那里你会找到如何将接口与实现完全分开,如何使用组合、继承、专门化或覆盖继承的谓词等。
这只是您在 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]
将消息委托 给对象,同时保留原始发件人。
SWI-Prolog 中的 PCE 系统也是 Prolog 中 OOP 的一个选项。它通常与 GUI 系统 xpce 相关联,但它实际上是一个基于 class 的通用 OO 系统。
现在,SWI prolog 有可以很好地与模块交互的指令。请参阅 The SWI prolog manual page on dicts,尤其是第 5.4.1.1 节:字典上的用户定义函数。
这允许您定义看起来完全像方法的东西,直到返回值(不常见但在 Prolog 中非常有用)。
与其他一些答案中讨论的不同,我个人认为逻辑编程和 OOP 范例是相互正交的:使用 OOP 模块化构建逻辑代码绝对没有坏处。 .
有一个叫做 context.pl 的东西作为另一个不相关项目的一部分实现。与 Logtalk 不同,它不需要编译,但它绝对只具有 Logtalk 的一小部分功能:
Context is an object-oriented programming paradigm for Prolog. It implements
contexts (namespaces), classes and instances. It supports various inheritance
mechanisms. Access to member predicates is regulated through public/protected
& private meta-predicates. We enable declarative static typing of class data
members.
/.../
CONTEXT implements a declarative contextual logic programming paradigm
that aims to facilitate Prolog software engineering. Short
description:
- We split up the global Prolog namespace into contexts, each having
their own facts and rules.
- We create a meta-language allowing you to declare metadata about
facts and rules in a context.
- We implement Classes and Instances, Public, Protected and Private
meta-predicates. We implement (Multiple) inheritance and cloning.
We implement operators enabling interaction with context.
我在某处读到,您可以将模块视为 Prolog 中的对象。我正在努力解决这个问题,如果它是一种很好的编码方式。
如果我有两个文件,一个定义 class 狗,另一个定义 class 来制作两个狗对象。
:- 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
更改行为关于这个。
另外,您将如何处理继承和重写方法等? 欢迎任何指向读数的指针。谢谢。
看看logtalk。它是 Prolog 的一种面向对象的扩展。
Logtalk 实际上是当今可用的突出的面向对象的 Prolog。 Paulo 将其作为 pack 提供,因此安装应该非常简单。
模块并不真正适合面向对象。它们更类似于名称空间,但没有嵌套。另外,ISO标准有点争议。
SWI-Prolog v7 引入了 dicts,一个扩展,至少可以处理该语言的历史问题,并按名称提供 'fields',以及 'methods' 的语法。但是,仍然没有继承...
编辑
我添加了 here a small example of object orientation in SWI-Prolog. It's an evolution of my 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 与原始图层最相似。例如,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 指令的示例:
...
N = N0.put(id, IdW),
...
内存分配方案在 allocator.pl 中处理。
Prolog 模块可以简单地解释为对象(具体来说,作为原型)。 Prolog 模块可以动态创建,有一个可以被视为它们身份的名称(因为它在 运行 会话中必须是唯一的,因为模块命名空间是平面的),并且可以有动态状态(使用局部的动态谓词到模块)。然而,在大多数系统中,它们提供弱封装,因为您通常可以使用显式限定调用任何模块谓词(也就是说,至少有一个系统 ECLiPSe 允许您锁定模块以防止以这种方式破坏封装)。也不支持将接口与实现分离或具有同一接口的多个实现(您可以以某种方式破解它,具体取决于 Prolog 模块系统,但它并不漂亮)。
如其他答案中所述,Logtalk 是 Prolog 的高度可移植的面向对象的扩展,支持大多数系统,包括 SWI-Prolog。 Logtalk 对象包含 Prolog 模块,从概念和实践的角度来看。 Logtalk 编译器支持模块功能的通用核心。您可以使用它,例如在没有模块系统的情况下在 Prolog 实现中编写模块代码。 Logtalk可以将模块编译为对象,支持对象与模块的双向调用
请注意,逻辑编程中的对象最好被视为一种代码封装和代码重用机制。就像模块一样。 OO 概念可以(并且已经)成功应用于其他编程范例,包括功能和逻辑。但这并不意味着一定要带来 imperative/procedural 概念。例如,实例与其class或原型之间的关系因为它的 parent 可以解释为指定 代码重用模式 而不是从 dynamic/state 的角度来看(事实上,在源自 imperative/procedural 语言的 OOP 语言中,一个实例只不过是一个美化的动态数据结构,其规范分布在它的 class 和它的 class superclass 之间es).
考虑到您的示例代码,您可以在接近您的公式的 Logtalk 中轻松地重新编码它,但也可以使用其他方式,其中最有趣的方式不使用动态功能。存储状态(如在动态状态中)有时是必要的,甚至可能是特定问题的最佳解决方案(Prolog 有动态谓词是有原因的!)但应该谨慎使用,并且只有在真正需要时才使用。使用 Logtalk 不会改变(也不打算改变)这一点。
我建议您查看内容广泛的 Logtalk 文档及其大量的编程示例。在那里你会找到如何将接口与实现完全分开,如何使用组合、继承、专门化或覆盖继承的谓词等。
这只是您在 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]
将消息委托 给对象,同时保留原始发件人。
SWI-Prolog 中的 PCE 系统也是 Prolog 中 OOP 的一个选项。它通常与 GUI 系统 xpce 相关联,但它实际上是一个基于 class 的通用 OO 系统。
现在,SWI prolog 有可以很好地与模块交互的指令。请参阅 The SWI prolog manual page on dicts,尤其是第 5.4.1.1 节:字典上的用户定义函数。
这允许您定义看起来完全像方法的东西,直到返回值(不常见但在 Prolog 中非常有用)。
与其他一些答案中讨论的不同,我个人认为逻辑编程和 OOP 范例是相互正交的:使用 OOP 模块化构建逻辑代码绝对没有坏处。 .
有一个叫做 context.pl 的东西作为另一个不相关项目的一部分实现。与 Logtalk 不同,它不需要编译,但它绝对只具有 Logtalk 的一小部分功能:
Context is an object-oriented programming paradigm for Prolog. It implements contexts (namespaces), classes and instances. It supports various inheritance mechanisms. Access to member predicates is regulated through public/protected & private meta-predicates. We enable declarative static typing of class data members.
/.../
CONTEXT implements a declarative contextual logic programming paradigm that aims to facilitate Prolog software engineering. Short description:
- We split up the global Prolog namespace into contexts, each having their own facts and rules.
- We create a meta-language allowing you to declare metadata about facts and rules in a context.
- We implement Classes and Instances, Public, Protected and Private meta-predicates. We implement (Multiple) inheritance and cloning. We implement operators enabling interaction with context.