跳转到内容

Prolog/高阶编程

来自维基教科书,开放的书籍,为开放的世界

高阶编程是指编写将其他程序作为输入和/或输出其他程序的程序。高阶编程在函数式编程中很常见,但也可以在 Prolog 中实现。

例如,假设您正在编写一个语音合成应用程序,它可以朗读电话号码。我们可能有一个谓词digit_english/2,它将 Prolog 整数 0 到 9 与英文单词(作为符号)相关联。

digit_english(0,zero).
digit_english(1,one).
digit_english(2,two).
...

现在,要翻译电话号码,定义

phone_english([],[]).
phone_english([Digit|Ds],[Word|Ws]) :-
    digit_english(Digit,Word),
    phone_english(Ds,Ws).

现在,假设我们要添加对另一种语言的支持,比如德语。你定义digit_german

digit_german(0,null).
digit_german(1,eins).
digit_german(2,zwei).
...

要翻译电话号码,使用phone_german

phone_german([],[]).
phone_german([Digit|Ds],[Word|Ws]) :-
    digit_german(Digit,Word),
    phone_german(Ds,Ws).

请注意phone_english/2phone_german中常见的递归模式。您可能以前多次看到过它,甚至多次编写过它。它可以概括为

  • 基本情况:将空列表与其自身相关联。
  • 递归情况:通过某个谓词将第一个列表的第一个元素与第二个列表的第一个元素相关联,然后对两个列表的尾部进行递归。

这种模式可以通过一个称为map/3的高阶谓词来捕获(但不是标准 Prolog 定义的)

map([],P,[]).
map([X|Xs],P,[Y|Ys]) :-
    Goal =.. [P,X,Y],
    call(Goal),
    map(Xs,P,Ys).

在 Prolog 中,我们不能写 P(X,Y),因为函数符的头部必须是符号。因此,我们使用中缀运算符=..,它将第一个参数与从其右参数构建的项统一起来,该参数必须是一个列表。第一个元素,它必须是一个符号,成为函数符,其余部分成为其参数。然后我们将call/1应用于我们构建的项以将其作为 Prolog 目标调用。请注意,许多 Prolog 实现也定义了call/2call/3、…内置谓词,它们从给定参数(第一个参数是主要函数符)构建一个可调用项,以便上面的代码示例也可以写成

map([],P,[]).
map([X|Xs],P,[Y|Ys]) :-
    call(P,X,Y),
    map(Xs,P,Ys).

现在我们可以将phone_english/2phone_german/2重新定义为

phone_english(P,E) :- map(P,digit_english,E).
phone_german(P,G) :- map(P,digit_german,G).

这两个谓词仍然在两个方向上工作,并且非确定性得以保留。

好处显而易见:通过使用map/3,我们避免了重复代码片段,因此我们的程序变得更短,更容易阅读和理解。

另请参阅

[编辑 | 编辑源代码]

以下研究论文是关于 Prolog 中的高阶编程,其中包含大量示例:[1]

华夏公益教科书