File: pendoc.md

package info (click to toggle)
swi-prolog 7.2.3%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 84,180 kB
  • ctags: 45,684
  • sloc: ansic: 330,358; perl: 268,104; sh: 6,795; java: 4,904; makefile: 4,561; cpp: 4,153; ruby: 1,594; yacc: 843; xml: 82; sed: 12; sql: 6
file content (295 lines) | stat: -rw-r--r-- 11,855 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# An overview of Pengines		{#pengine-overview}

This package provides a  powerful   high-level  programming  abstraction
implemented on top of SWI-Prolog's thread   predicates  [1] and its HTTP
client and server libraries [2]. The package makes it easy to create and
query _Prolog engines_ (or _Pengines_  for   short),  over HTTP, from an
ordinary Prolog thread, from a pengine, or  from JavaScript running in a
web  client.  Querying  follows   Prolog's  default  one-tuple-at-a-time
generation of solutions. I/O is also supported.

Possible  applications  abound,  but  in    particular  three  kinds  of
applications stick out: 1) The package provides   us with a useful point
of  departure  for  the  design  and  implementation  of  more  advanced
Prolog-based agent programming platforms, 2) it  suggests an elegant and
very straightforward approach to the building of a Semantic Web which is
Prolog-based in a very radical sense, and,   3)  it constitutes an ideal
way to interface Prolog with JavaScript,   the programming language most
commonly available in web browsers.

A pengine is comprised of:

    * A Prolog thread

    * A dynamic clause database, private to the pengine, into which
      other processes may assert clauses.  These clauses reside in the
      module =pengine_sandbox=.

    * A message queue for incoming requests

    * A message queue for outgoing responses

Everything needed to work with  pengines   is  included  in the package,
including  a  JavaScript  library  for  creating  and  interacting  with
pengines from a web  client.  However,  the   web  server  (in  the file
examples/server.pl) should only be regarded as a minimal example.

Underlying the design of the  package  is   a  careful  analysis  of the
conversations taking place between Prolog and a   user (which could be a
human or another  piece  of  software).   Such  conversations  follow  a
communication protocol that we refer to as the Prolog Transport Protocol
(PLTP).  The  protocol  has  been  modelled    by  means  of  so  called
_communicating finite-state machines_ [3]. A  slight modification of the
protocol -- referred to as PLTP(HTTP) --   enables  us to synchronize it
with HTTP. The diagram  below   depicts  the  communicating finite-state
machines for PLTP(HTTP) and HTTP. Labels  in bold indicate requests, and
labels with a slash in front indicate responses.

[[pltpsynch.png]]

The diagram below depicts a PLTP run (on the right) corresponding to a
user's interaction with Prolog (on the left). `1234' is the Pengine's
identifier, which is a UUID in the actual implementation.

[[pltpruncolour.png]]

As for the relations between pengines, and   for the time being, we have
opted for a simple _master-slave   architecture_.  Once the master/slave
relationships are established, the direction of   control is always from
the master to the slaves. One or  more pengines can be _orchestrated_ by
a common master which can be an ordinary Prolog thread, another pengine,
or a JavaScript process. A slave  is   always  a pengine, running either
locally or remotely with respect to its   master.  Subject to a setting,
slaves are also dependent on their masters in the sense that if a master
terminates, so do its slaves.

[[penarch.png]]

The transport format is different depending on the nature of the master.
If the master is a JavaScript process,   it  will (by default) formulate
its requests using Prolog syntax,  and   get  responses  back as Prologs
terms encoded in JSON. If  the  master   is  a  Prolog process (a Prolog
thread or a pengine) it will (again  only by default) get responses back
as Prolog terms.

Most of the pengine predicates are   deterministic, yet they can control
one or more pengines solving possibly non-deterministic queries. But the
package also offers a number of   non-deterministic predicates, built on
top of the deterministic ones, that can  solve queries "the Prolog way",
binding query variables in the process, backtracking for more solutions.
Of these predicates, pengine_rpc/3 is the   most  important. By means of
pengine_rpc/3 a pengine running in a pengine   server A can call and try
to solve a query in the  context   of  another  pengine server B, taking
advantage of the data being offered by B,  just as if the data was local
to A. Thus, in theory, a Prolog program, be it a pure Horn clause theory
or not, can be as big as the Web.  This is something that should make us
think about a _Semantic Web_, especially  when we consider the excellent
fit between the Pengine library and   SWI-Prolog's  Semantic Web Library
[4]. Adding Pengines functionality to the Cliopatria platform [5] is
 straightforward.

A note about safety: Because PLTP is  layered   on  top  of HTTP, it may
utilize any standard HTTP security feature,  such as HTTP authentication
or SSL. Moreover, subject to  a   setting,  the library uses safe_goal/1
[6], which determines whether it is safe for   a slave pengine to try to
solve queries asked by a master.


## Pengine references		{#pengine-references}

 1. http://www.swi-prolog.org/pldoc/man?section=threads

 2. http://www.swi-prolog.org/pldoc/package/http.html

 3. D. Brand and P. Zafiropulo. On communicating finite-state machines.
   _Journal of the ACM_, 30(2):323-342, 1983.

 4. http://www.swi-prolog.org/pldoc/package/semweb.html

 5. http://cliopatria.swi-prolog.org/home

 6. http://www.swi-prolog.org/pldoc/doc/home/vnc/prolog/lib/swipl/library/sandbox.pl



## Pengine by examples		{#pengine-examples}

In this example we load the pengines library and use pengine_create/1 to
create a slave pengine in a remote   pengine server, and inject a number
of clauses in it. We then  use   pengine_event_loop/2  to start an event
loop that listens for three kinds of  event terms. Running =main/0= will
write  the  terms  q(a),  q(b)  and   q(c)  to  standard  output.  Using
pengine_ask/3 with the option template(X) would instead produce the output
=a=, =b= and =c=.

==
:- use_module(library(pengines)).

main :-
    pengine_create([
        server('http://pengines.swi-prolog.org'),
        src_text("
            q(X) :- p(X).
            p(a). p(b). p(c).
        ")
    ]),
    pengine_event_loop(handle, []).


handle(create(ID, _)) :-
    pengine_ask(ID, q(_X), []).
handle(success(_ID, [X], false)) :-
    writeln(X).
handle(success(ID, [X], true)) :-
    writeln(X),
    pengine_next(ID, []).
==

Here is another example, showing  how  to   create  and  interact with a
pengine from JavaScript in a way that seems ideal for Prolog programmers
and JavaScript programmers alike.   Loading the page
brings up the browser's prompt dialog, waits   for the user's input, and
writes that input in the browser  window.   If  the input was 'stop', it
stops there, else it repeats. Note that   I/O  works as expected. All we
need  to  do  is  to   use    pengine_input/1   instead  of  read/1  and
pengine_output/1 instead of write/1.

==
<html lang="en">
    <head>
        <script src="/vendor/jquery/jquery-2.0.3.min.js"></script>
        <script src="/pengine/pengines.js"></script>
        <script type="text/x-prolog">

            main :-
                repeat,
                pengine_input(X),
                pengine_output(X),
                X == stop.

        </script>
        <script>
            var pengine = new Pengine({
                oncreate: handleCreate,
                onprompt: handlePrompt,
                onoutput: handleOutput
            });
            function handleCreate() {
                pengine.ask('main');
            }
            function handlePrompt() {
                pengine.input(prompt(this.data));
            }
            function handleOutput() {
                $('#out').html(this.data);
            }
        </script>
    </head>
    <body>
        <div id="out"></div>
    </body>
</html>
==

Our third example shows that a non-deterministic predicate can be called
remotely by means of pengine_rpc/2,  yet   behave  exactly  as if called
locally:

==
?- use_module(library(pengines)).

?- member(X, [a, b, c, d]),
   pengine_rpc('http://pengines.org', p(X), [
       src_list([p(b), p(c), p(d), p(e)])
   ]),
   member(X, [c, d, e, f]).
X = c ;
X = d.
?-
==

## Making predicates available to clients {#pengine-server-code}

The code sent to a pengine is  executed   in  the  context of the module
=pengine_sandbox= and the safety of goals is validated using safe_goal/1
prior to execution. Any  pengine  has   access  to  the  safe predicates
defined in library(sandbox). If a server  wishes   to  extend the set of
predicates, it must:

  1. Define one or more modules that export the desired additional
     predicates.

  2. Makes this code available to the sandbox using the call below,
     assuming that the additional predicates are defined in the
     Prolog module file domain_predicates.pl

     ==
     :- use_module(pengine_sandbox:domain_predicates).
     ==

  3. Register *safe* foreign predicates with library(sandbox), i.e.,
     predicates that do not have side effects such as accessing the
     file system, load foreign extensions, define other predicates
     outside the sandbox environment, etc.

     Note that the safety of Prolog predicate can typically be
     proven by library(sandbox).  This may not be the case if
     untracktable forms of meta-calling are used.  In this case
     it is adviced to avoid such code.  If this is not possible,
     the code must be carefully reviewed by hand and of proven to
     be safe it may be registered with the sandbox library.

For example, basic RDF access can be  granted to pengines using the code
below.  Please  *study  the  sandboxing  code  carefully  before  adding
declarations*.

  ==
  :- use_module(pengine_sandbox:library(semweb/rdf_db)).
  :- use_module(library(sandbox)).

  :- multifile sandbox:safe_primitive/1.

  sandbox:safe_primitive(rdf_db:rdf(_,_,_)).
  ==


## Mapping Prolog terms into JSON	{#prolog-canonical-json}

In Prolog, solutions to queries are given as bindings which map variable
names into Prolog terms. A  programmer   using  Pengines in a JavaScript
evironment needs to understand how bindings are converted into JSON. For
example, the programmer needs to understand  that the second solution to
=|append(Xs,   Ys,   [a,b,c])|=    is    given     by    the    bindings
=|['Xs'=[a],'Ys'=[b,c]]|= and that these binding   can be represented in
JSON as =|{"Xs":["a"], "Ys":["b","c"]}|=.

Pengines defines the following mapping between   ground Prolog terms and
JSON.

    * A Prolog atom is mapped to a JSON string.
    * A Prolog number is mapped to a JSON number.
    * A Prolog list is mapped to a JSON array.
    * The Prolog terms =|@(true)|= and =|@(false)|= are mapped to the
      JSON constants =true= and =false=, respectively.
    * The Prolog term =|@(null)|= is mapped to the JSON constant =null=.
    * A Prolog term json(NameValueList), where `NameValueList` is a
    list of `Name=Value` pairs, is mapped to a JSON object.

    * Any other complex Prolog term `T` is mapped to a JSON object of
    the form =|{"functor": F, "args": A}|= where `F` is a string
    representing the functor of `T` and `A` is the list of JSON values
    representing `T`s arguments.


## Pengine settings		{#pengine-settings}

Settings currently recognized by the Pengines library:


| *Name*		   | *Type*	| *Default* | *Description*						      |
| max_session_pengines	   | integer	| 1	    | Maximum number of pengines a client can create.  -1 is infinite |
| time_limit		   | number	| 60	    | Maximum time between output (in seconds)			      |
| allow_from		   | list(atom)	| [*]	    | Specify allowed IP addresses				      |
| deny_from		   | list(atom)	| []	    | Specify denied IP addresses.  Applied after =allow_from=.	      |

# Pengine libraries		{#pengine-libs}