Object Oriented Programming in SWI-Prolog

I read somewhere that you can think of modules as objects in Prolog. I am trying to ponder this, and if it is a good way of coding.

If I have two files, one of which defines the dog of the class, and then the other, which uses this class to create two dog objects.

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

second file

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

This makes a dog dog a dog that is a basset dog and makes it bark 5 times. Then I make another dog thing, Dog2, which is a bloodhound, and I also do it bark. I understand that in oop you have objects that have behavior and state. So, now I have two objects with different behavior based on their own states, but at the moment I save the state of the objects in Dog variables, where they can be seen by the code in the main program. Is there a way to hide the state of objects, for example, to have private variables? For example, I might want to keep the has_barked state for each dog object, which would be true if it barked earlier in the program and false otherwise, and then changed the behavior of bark/1 based on this.

Also, how would you handle inheritance and override methods, etc.? Any read pointer is appreciated. Thanks.

+8
oop module prolog swi-prolog
source share
6 answers

Just an example of one of the possible reimplementations of your sample code in Logtalk. It uses prototypes for simplicity, but it still illustrates some key concepts, including inheritance, default predicate definitions, static and dynamic objects, and parametric objects.

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

Some sample queries:

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

Some notes. ::/2 is a message sending control construct. The target {Object}::Message simply proves Object using a regular Prolog database and then sends the Message message to the result. The [Object::Message] target delegates the message to the object while retaining the original sender.

+7
source share

Prolog modules can be trivially interpreted as objects (in particular, as prototypes). Prolog modules can be dynamically created, have a name that can be regarded as its identity (since it must be unique in the current session, since the module namespace is flat) and can have a dynamic state (using dynamic predicates local to the module). However, on most systems, they provide weak encapsulation in the sense that you can usually call any module predicate using explicit qualifications (in this case, at least one system, ECLiPSe, allows you to block the module to prevent encapsulation from being interrupted in this way). There is also no support for the interface branch from the implementation or the presence of several implementations of the same interface (you can somehow hack it, depending on the system of the Prolog module, but this is not very).

Logtalk, as mentioned in other answers, is a portable object-oriented extension for Prolog that supports most systems, including SWI-Prolog. Logtalk objects include Prolog modules, both conceptually and from a practical point of view. The Logtalk compiler supports the common core functions of the module. You can use it, for example. write module code in Prolog implementations without a modular system. Logtalk can compile modules as objects and supports bidirectional calls between objects and modules.

Note that objects in Logic Programming are best viewed as a mechanism for encapsulating code and reusing code. Just like modules. OO concepts can (and have been) successfully applied in other programming paradigms, including functional and logical. But this does not mean that imperative / procedural concepts need to be introduced. For example, the relationship between an instance and its class, or between a prototype as its parent, can be interpreted as indicating a code reuse pattern instead of being visible from a dynamic / state point of view (in fact, in OOP languages โ€‹โ€‹derived from imperative / procedural languages , an instance is more than an illustrious dynamic data structure whose specification is shared between its class and its class classes).

Given your sample code, you can easily transcode it into Logtalk next to your wording, but also in other ways, the most interesting of which are the lack of dynamic functions. Saving state (as in a dynamic state) is sometimes necessary and may be the best solution for specific tasks (Prolog has dynamic predicates for some reason!), But they should be used with caution and only when it is really necessary. Using Logtalk does not change (or intend to change) that.

I suggest you familiarize yourself with the extensive Logtalk documentation and its many programming examples. There you will find how, for example, to cleanly separate the interface from the implementation, how to use composition, inheritance, specialize or redefine inherited predicates, etc.

+4
source share

Logtalk is actually the famous Object Oriented Prolog available today. Paulo made it available as a pack , so installation should be very simple.

Modules are not suitable for orientation of objects. They are more like namespaces, but without nesting. In addition, the ISO standard is a bit of a contradiction.

SWI-Prolog v7 introduced dicts , an extension that at least handles the historical problem of the language and provides accessible โ€œfieldsโ€, by name and syntax for โ€œmethodsโ€. But still, no inheritance ...

change

I have added a small example of object orientation in SWI-Prolog here. This is the evolution of my test application for creating family trees.

By comparing the sources of genealogy.pl, you can evaluate how the latest version uses the module specifier and not the directive: - multifile, and then can work with several trees.

You can see that the calling module is passed by the building code of the graph, and have optional or mandatory predicates that are called by the module qualification:

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

optional predicates must be called as

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

Note that predicates are not exported by applicative modules. By exporting them, AFAIK disrupts the orientation of the object.

===========

Another, perhaps more trivial, example of object orientation is the pqGraphviz_emu module, where I created a simple mental replacement of system level objects.

I will explain: pqGraphviz is a tiny layer - written in Qt - above the Graphviz library. Graphviz - albeit in C - has an object-oriented interface. Indeed, the API allows you to create appropriate objects (graphs, nodes, links), and then assign attributes to them. My layer is trying to keep the API most similar to the original. For example, Graphviz creates a node with

 Agnode_t* agnode(Agraph_t*,char*,int); 

I wrote using the C ++ interface

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

We exchange pointers, and I set up the Qt metatep tool to handle input ... but since the interface is pretty low, I usually have a tiny middle layer that provides a more applicative look, and this middle level interface called from 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). 

In this snippet you can see an example of SWI-Prolog v7 dicts:

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

The memory allocation scheme is processed in allocator.pl.

+3
source share

Take a look at logtalk . This is a kind of object-oriented extension to Prolog.

http://logtalk.org/

+2
source share

PCE in SWI-Prolog is also an option for OOP in Prolog. This is usually associated with xpce, a GUI system, but it is actually an OO system with a common class.

+2
source share

There are currently dicts in the SWI prolog that interact well with modules. See the SWI prolog man page for dicts , especially section 5.4.1.1: User functions for dicts.

This allows you to define things that look exactly like methods, right down to the return values โ€‹โ€‹(unusual, but very useful in Prolog).

Unlike discussed in some other answers, I personally think that logical programming and OOP paradigms are orthogonal to each other: it definitely does not interfere with structuring your logic code using OOP modularity ...

0
source share

All Articles