Web Prolog 中的并发和分布式编程
SWI Prolog 是一个开源的 Prolog 实现,具有良好的文档和许多有趣的库。最近发布的一个库是 Web Prolog 。虽然被标记为“Web Logic 编程语言”,但 github 存储库中的实现在 SWI Prolog 下运行,并添加了 Erlang 风格的分布式并发。 存储库中有一本 PDF Book ,其中详细介绍了该系统。在这篇文章中,我将探讨一些功能。
安装 SWI Prolog
第一步是安装并运行 SWI Prolog 开发版本。有 关于这方面的文档 ,但以下是从 SWI Prolog github 存储库下载和构建的基本步骤:
$ git clone https://github.com/SWI-Prolog/swipl-devel
$ cd swipl-devel
$ ./prepare
... answer yes to the various prompts ...
$ cp build.tmpl build
$ vim build
...change PREFIX to where you want to install...
$ make install
$ export PATH=<install location>/bin:$PATH
构建可选库时可能会出现错误。可以忽略它们或 查看依赖项 以了解如何安装。
新安装的 SWI Prolog 版本可以通过以下方式运行:
$ swipl Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.19-47-g4c3d70a09) SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software. Please run ?- license. for legal details.
For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?-
语法,我建议您阅读 Basic Concepts 中的 The Power of Prolog 如果您不熟悉 Prolog
启动 Web Prolog 节点
安装 Web Prolog 库涉及克隆 github 存储库并加载其中包含的 prolog 库。该系统包括一个基于 Web 的教程和 IDE,其中每个连接的网页都是一个 Web prolog 节点,可以向其他 Web prolog 节点发送和接收消息。我稍后会提到这一点,但不会在这篇文章中详细介绍它 - 现在,一些简单的示例将只使用 SWI Prolog REPL。我们需要运行两个节点,这可以通过在不同的机器上运行来完成。在每台计算机上运行:
$ git clone https://github.com/Web-Prolog/swi-web-prolog/
$ cd swi-web-prolog
$ swipl
...
?- [web_prolog].
...
[web_prolog].
命令将加载 Web Prolog 库。加载后,我们需要实例化一个节点,该节点启动 socket 服务器和处理消息传递的机制:
?- node.
% Started server at http://localhost:3060/
true.
这将在默认端口上启动一个节点 3060
。如果在同一台机器上运行多个节点,则可以通过将参数传递给 node
:
?- node(localhost:3061).
% Started server at http://localhost:3061/
true.
在本文的其余部分,我指的是 node A
和节点 B
对于上面开始的两个节点。
基本示例
每个进程都有一个可用于引用它的 ID。它是通过以下方式获得的 self
:
?- self(Self).
Self = thread(1)@'http://localhost:3061'.
我们可以使用 !
,与 Erlang 一样,它需要一个要发送到的进程和要发送的数据:
?- self(Self),
Self ! 'Hello World'.
这会将 Hello World 发布到当前进程邮箱。可以使用 receive
。这需要一系列要匹配的模式,如果进程邮箱中的项目与该模式匹配,则需要运行代码。与 Erlang 一样,它是一个选择性接收 - 它将查看邮箱中匹配模式的消息,而不仅仅是最上面的消息:
?- receive({ M -> writeln(M) }).
Hello World
M = 'Hello World'.
请注意 receive
规则括在 ' { ... }
' 的 ID 中,由要匹配的模式和要运行的代码组成,以 ->
。模式中的变量被分配它们匹配的值。在这种情况下 M
设置为 Hello World
因为这是我们之前发送的信息。更复杂的模式是可能的:
?- $Self ! foo(1, 2, bar('hi', [ 'a', 'b', 'c' ])). Self = thread(1)@'http://localhost:3061'.
?- receive({ foo(A, B, bar(C, [D | E])) -> true }). A = 1, B = 2, C = hi, D = a, E = [b, c].
在这里,我使用了 SWI Prolog REPL 的一个功能 - 在变量前面加上 $
将使用在前面的 REPL 命令中分配给该变量的值。这还会在列表的头部和尾部显示与 [Head | Tail]
语法。
要发送到另一个进程,我们只需要使用进程标识符。在本例中,我可以从节点 A 向节点 B 发送消息:
# In Node A
?- self(Self).
Self = thread(1)@'http://localhost:3060'.
# This call blocks
?- receive({ M -> format('I got: ~s~n', [M])}).
# In Node B
?- thread(1)@'http://localhost:3060' ! 'Hi World'.
true.
# Node A unblocks
I got: Hi World
M = 'Hi World'.
生成进程
用 spawn
要生成新进程,则会在节点上同时运行。它将有自己的进程 ID:
?- spawn((self(Self),
format('My process id is ~w~n', [Self]))).
true.
My process id is 21421552@http://localhost:3060
要在进程中运行的代码将作为第一个参数传递给 spawn
并且必须在括号之间。可选的第二个参数将向调用者提供进程 ID:
?- spawn((self(Self),
format('My process id is ~w~n', [Self])),
Pid).
My process id is 33869438@http://localhost:3060
Pid = '33869438'.
可以提供第三个参数来传递选项来控制生成。其中包括链接进程、监控进程或注册名称以在注册表中识别进程的能力。
使用 REPL,我们可以定义新规则并在进程中运行它们,方法是通过查阅文件或通过 [user]
。以下代码将定义 'ping' 服务器进程的代码:
server :-
receive({
ping(Pid) -> Pid ! pong, server
}).
这可以添加到文件中,并使用 consult
,或者可以直接使用 [user]
(请注意使用 Ctrl+D 退出用户输入):
# on Node A
?- [user].
|: server :-
|: receive({
|: ping(Pid) -> Pid ! pong, server
|: }).
|: ^D
% user://1 compiled 0.01 sec, 1 clauses
true.
server
阻止接收查找 ping(Pid)
消息。它会发送一个 pong
message 返回到它从消息中提取的进程 ID,并使用 tail 调用递归调用自身。在具有以下位置的节点上运行:
# on node A
?- spawn((server), Pid).
Pid = '18268992'.
通过引用 Pid 和节点地址从另一个节点发送消息:
# on node B ?- self(Self), 18268992@'http://localhost:3060' ! ping(Self). Self = thread(1)@'http://localhost:30601 .
?- flush.
% Got pong
true.
这 flush
call 将从进程队列中删除所有消息并显示它们。在这种情况下, pong
来自其他节点服务器的回复。
在远程节点上生成
Web Prolog 提供了生成进程以在远程节点上运行的功能。这是通过传递 node
选项设置为 spawn
。要进行演示,请输入以下内容以创建一个 add
规则:
# on node A
?- [user].
|: add(X,Y,Z) :- Z is X + Y.
|: ^D
% user://1 compiled 0.01 sec, 1 clauses
true.
这将创建一个 'add' 谓词,其工作方式如下:
# on node A
?- add(1,2,Z).
Z = 3.
我们可以通过生成一个在节点 A 中执行代码的远程进程,从节点 B 调用此谓词。在节点 B 中,它看起来像这样:
# on node B
?- self(Self),
spawn((
call(add(10, 20, Z)),
Self ! Z
), Pid, [
node('http://localhost:3060'),
monitor(true)
]),
receive({ M -> format('Result is ~w~n', [M])})
Result is 30
Self = thread(1)@'http://localhost:3061',
Pid = '24931627'@'http://localhost:3060',
M = 30.
通过 node
作为选项 spawn/3
导致在该引用的节点上生成一个进程,并且代码在那里运行。在代码中,我们调用 add
谓词,并通过将其发送到节点 B 的标识符来返回它。这 monitor
选项提供有关远程进程的状态信息 - 包括在进程退出时获取消息。叫 flush
on node B 显示节点 A 上的进程退出:
# on Node B
?- flush.
% Got down('24931627'@'http://localhost:3060',exit)
true.
请注意 spawn
调用 where add
不是直接调用的,而是 call
用于调用它。出于某种原因,Web Prolog 需要 add
存在于节点 B 上,即使它未被调用。例如,下面给出了节点 B 上的错误:
# on node B
?- self(Self),
spawn((
add(10, 20, Z),
Self ! Z
), Pid, [
node('http://localhost:3060'),
monitor(true)
]),
receive({ M -> format('Result is ~w~n', [M])})
ERROR: Undefined procedure: add/3 (DWIM could not correct goal)
我不确定这是 web prolog 中的错误还是我正在做的事情。
RPC 调用
前面的示例可以使用 web prolog 提供的 RPC 函数进行简化。可以执行来自节点 B 的同步 RPC 调用,这与上述调用大致等效:
?- rpc('http://localhost:3060', add(1,2,Z)).
Z = 3.
或者异步使用 promises:
?- promise('http://localhost:3060', add(1,2,Z), Ref). Ref = '119435902516515459454946972679206500389'.
?- yield($Ref, Answer).
Answer = success(anonymous, [add(1, 2, 3)], false),
Ref = '119435902516515459454946972679206500389'.
升级进程
下面的示例基于 我为 Wasp Lisp 所做的 示例,是一个维护可以递增、递减或检索的计数的进程:
server(Count) :-
receive({
inc -> Count2 is Count + 1, server(Count2);
dec -> Count2 is Count - 1, server(Count2);
get(Pid) -> Pid ! value(Count), server(Count)
}).
定义后,它可以在节点 A 中生成:
?- spawn((server(0)), Pid).
Pid = '33403644'.
并从节点 B 调用,代码如下:
?- self(Self), 33403644@'http://localhost:3060' ! get(Self).
Self = thread(1)@'http://localhost:3061'.
?- flush.
% Got value(0)
true.
?- 33403644@'http://localhost:3060' ! inc.
true.
?- self(Self), 33403644@'http://localhost:3060' ! get(Self).
Self = thread(1)@'http://localhost:3061'.
?- flush.
% Got value(1)
true.
我们可以修改服务器,使其接受 upgrade
消息,其中包含服务器将执行的新代码:
server(Count) :-
writeln("server receive"),
receive({
inc -> Count2 is Count + 1, server(Count2);
dec -> Count2 is Count - 1, server(Count2);
get(Pid) -> Pid ! value(Count), server(Count);
upgrade(Pred) -> writeln('upgrading...'), call(Pred, Count)
}).
运行服务器并向其发送一些消息。然后定义一个 new_server
在节点 A 的 REPL 上:
new_server(Count) :-
writeln("new_server receive"),
receive({
inc -> Count2 is Count + 1, new_server(Count2);
dec -> Count2 is Count - 1, new_server(Count2);
mul(X) -> Count2 is Count * X, new_server(Count2);
get(Pid) -> Pid ! value(Count), new_server(Count);
upgrade(Pred) -> writeln(Pred), writeln(Count), call(Pred, Count)
}).
并发送一个 upgrade
消息发送到现有服务器:
# on node A
?- $Pid ! upgrade(new_server).
upgrading...
new_server receive
Now the server understands the mul
message without having to be shut down and restarted.
现在,服务器可以理解 mul
消息,而无需关闭并重新启动。
Web IDE 和教程
有一个基于 Web 的 IDE 和基于 SWISH( 在线 SWI-Prolog 系统)的教程。要在本地节点上启动此操作,请从 Web prolog 源运行以下脚本:
$ cd web-client
$ swipl run.pl
现在访问 http://localhost:3060/apps/swish/index.html
在 Web 浏览器中。这将在左侧显示一个教程,在右侧显示一个 REPL。每个加载的网页都有自己的命名空间和本地环境。您可以向各个网页和本地节点中运行的进程发送消息或从这些页面发送消息。它使用 websockets 向实例发送数据或从实例发送数据。
结论
Web Prolog 系统有很多内容,SWI Prolog 的实现有一些粗糙的边缘,但可用于试验和感受系统。我希望随着我对它越来越熟悉,可以写更多关于它的一些功能以及 SWI Prolog。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 函数式编程中的 Y 组合子
下一篇: 不要相信一个熬夜的人说的每一句话
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论