Prolog - 列表的非常规 cons 语法

26

我在Lee Naish的论文Higher-order logic programming in Prolog中遇到了一个陌生的Prolog语法。这是论文中的第一个代码示例:

% insertion sort (simple version)
isort([], []).
isort(A.As, Bs) :-
    isort(As, Bs1),
    isort(A, Bs1, Bs).

% insert number into sorted list
insert(N, [], [N]).
insert(N, H.L, N.H.L) :-
    N =< H.
insert(N, H.LO, H.L) :-
    N > H,
    insert(N, LO, L).

我的困惑在于isort(A.As, Bs) :-中的A.As。从上下文来看,它似乎是列表的另一种构造语法,相当于isort([A|As], Bs) :-
同时,N.H.L似乎是一个更方便的表示[N|[H|L]]的方式。
但是SWI Prolog不接受这种不寻常的语法(除非我做错了什么)。
有人认识它吗?我的假设是正确的吗?哪个Prolog解释器接受它作为有效的语法?

这篇论文中使用的是哪个Prolog? - Chetter Hummin
我查阅了那份文件,但不幸的是在里面没有找到相关的参考资料! - Nick Knowlson
可能只是一些语法糖。 - Chetter Hummin
3个回答

33

点运算符最早出现在1972年的Prolog系统中,该系统是用Algol-W编写的,有时被称为Prolog 0。它受到LISP系统中类似符号的启发。以下示例摘自Alain Colmerauer和Philippe Roussel的论文The birth of Prolog - Prolog的创造者。

+ELEMENT(*X, *X.*Y).
+ELEMENT(*X, *Y.*Z) -ELEMENT(*X, *Z).

当时,[]曾经是NIL。下一个版本Prolog I由Battani & Meloni用Fortran编写时,所有内容保持不变。

然后DECsystem 10 Prolog使用情况区分原子和变量,并引入了方括号表示法,取代了nilX.Xs,其中在DECsystem 10的后续版本中使用[X|Xs]作为替代方案。在ISO Prolog中,只有[X|Xs].(X,Xs)和作为规范语法的'.'(X,Xs)

请注意,在ISO Prolog中,“.”有很多不同的角色。

  • 结束标记,后面跟着一个%或者布局字符,如SPACE、NEWLINE、TAB。

  • 浮点数中的小数点,例如3.14159

  • 图形标记字符,形成图形标记,如=..

因此,如果您现在将.声明为中缀运算符,您必须非常小心。无论您写什么以及Prolog系统将读取什么都要小心。一个额外的空格可以改变一个术语的含义。考虑两个列表中的数字,使用这两种表示法:

[1,2.3,4]. [5].
1 .2.3.4.[]. 5.[].

请注意,在1后面必须添加一个空格。在这种情况下,数字前面的额外空格可能会改变术语的含义。就像这样:
[1|2.3]. [4]. 5. [].
1 .2.3. 4.[]. 5. [].

这是另一个例子,可能更具有说服力:
[1,-2].
1.(-2).[].

负数在点号列表中需要用括号。

今天,只有 YAP 和 XSB 仍然默认提供中缀 .,并且它们的处理方式不同。XSB 甚至无法识别上述点语法:您需要在一些非负数周围加上圆括号。

您写道 N.H.L 看起来是说 [N|[H|L]] 的更方便的方法。在 ISO Prolog 中简化这样的表达式有一个简单的经验法则:每当您在列表中看到紧接着彼此的标记 |[,您可以将它们替换为 ,(并删除右侧对应的 ])。所以现在您可以写成:[N,H|L],这看起来并不那么糟糕。

你也可以反向运用这个规则。如果我们有一个列表[1,2,3,4,5],我们可以使用|作为“切割刀”,如下所示:[1,2,3|[4,5]]


另外一点需要注意,既然你正在阅读Naish的论文:与此同时,现在已经很清楚只需要call/N!而且ISO Prolog支持call/1 call/2直到call/8

4
哇,多么详尽的答案啊,非常感谢! [N,H|L]确实更好,我不知道它是有效的。我也很感激您指出那个交换,那真的很有趣。 - Nick Knowlson
4
你可能会感兴趣了解lambda函数如何与call/N配合使用 - false
3
我刚刚查过了,在麦卡锡关于Lisp的第一篇论文中,这个点对出现了 - http://www.cs.unm.edu/~luger/cs451/resources/recursive.pdf(第3节)。 - andrew cooke
1
@Will Ness:谢谢。不,我不知道那个规则(我总是希望能找到一个好的SO律师……)。有点可惜:回复中应该还有一些更多的细节来理解“-1”的精确语法。 - false
2
啊,好的,我告诉你就好了。 :) 你有两个选择(但我不是律师):添加另一个答案或在进行另一次编辑后标记你的答案并请求管理员取消社区维基状态。但这是一个极端的措施(我曾经因同样的原因这样做过)。或者你可以在元社区上询问这个问题。 :) - Will Ness
显示剩余2条评论

9

是的,你说得对,点号是列表 cons 中缀运算符。它实际上是ISO Prolog标准所要求的,但通常被隐藏起来了。我一段时间之前就发现了(和使用了)这个语法:

:- module(eog, []).
:- op(103, xfy, (.)).

% where $ARGS appears as argument, replace the call ($ARGS) with a VAR
% the calle goes before caller, binding the VAR (added as last ARG)
funcs(X, (V, Y)) :-
    nonvar(X),
    X =.. W.As,

    % identify meta arguments
    (   predicate_property(X, meta_predicate M)
        % explicitly exclude to handle test(dcg)
        % I'd like to handle this case in general way...
    ,   M \= phrase(2, ?, ?)
    ->  M =.. W.Ms
    ;   true
    ),

    seek_call(As, Ms, Bs, V),
    Y =.. W.Bs.

% look for first $ usage
seek_call([], [], _Bs, _V) :-
    !, fail.
seek_call(A.As, M.Ms, A.Bs, V) :-
    M @>= 0, M @=< 9, % skip meta arguments
    !, seek_call(As, Ms, Bs, V).
seek_call(A.As, _, B.As, V) :-
    nonvar(A),
    A = $(F),
    F =.. Fp.FAs,
    (   current_arithmetic_function(F) % inline arith
    ->  V = (PH is F)
    ;   append(FAs, [PH], FBs),
        V =.. Fp.FBs
    ),
    !, B = PH.
seek_call(A.As, _.Ms, B.As, V) :-
    nonvar(A),
    A =.. F.FAs,
    seek_call(FAs, Ms, FBs, V),
    !, B =.. F.FBs.
seek_call(A.As, _.Ms, A.Bs, V) :-
    !, seek_call(As, Ms, Bs, V).

:- multifile user:goal_expansion/2.
user:goal_expansion(X, Y) :-
    ( X = (_ , _) ; X = (_ ; _) ; X = (_ -> _) )
    -> !, fail % leave control flow unchanged (useless after the meta... handling?)
    ;  funcs(X, Y).

/* end eog.pl */

我被建议不要这样做。实际上,[A | B] 语法是 . 运算符的演化,为了可读性而引入。
OT:那是什么代码?
上面的代码是我试图用函数来美化 Prolog 的尝试。也就是说,通过 $,根据需要引入所需的临时变量(例如,在算术表达式中)。
fact(N, F) :-
     N > 1 -> F is N * $fact($(N - 1)) ; F is 1.

每个$都引入了一个变量,在展开后,我们得到了一个更传统的fact/2

?- listing(fact).
plunit_eog:fact(A, C) :-
    (   A>1
    ->  B is A+ -1,
        fact(B, D),
        C is A*D
    ;   C is 1
    ).

我们有很多表达式,这些表达式可能会很有用...

在IT技术方面。


非常感谢您的答复!我不理解 $ 是如何工作的,但在 $fact 的例子中非常引人注目-我需要花些时间来了解这段代码。 - Nick Knowlson
为了测试该代码,您的Prolog必须提供goal_expansion/2和modules。这两者都是事实上的扩展,而不是ISO批准的。很抱歉。实际上,相对于可用实现,ISO Prolog是一种非常受限制的语言,这是Prolog的主要问题之一... - CapelliC
2
@chac:系统在发布到发布的基础上仍然会改变goal_expansion/2等的含义。这几乎是不可能标准化的。 - false
@false: 它的使用并不是很容易。SWI-Prolog在重新编译尝试性代码时很容易锁定,或者更好的说,生成大量的重新定义消息并进入重新编译循环(我想)。幸运的是,Jan在SWI-Prolog列表上最近发表的一篇文章澄清了这个问题。 - CapelliC
@chac:SWI和SICStus之间的含义仍然不同。如果系统符合现有标准,那就太好了。有进展,但还有很多工作要做。 - false

8

这个语法来自NU-Prolog。请参见此处。它可能只是将正常的列表函数'.'/2重新定义为中缀运算符,而无需使用尾随空列表:

?- L= .(a,.(b,[])).
L = [a,b]
Yes (0.00s cpu)
?- op(500, xfy, '.').
Yes (0.00s cpu)
?- L = a.b.[].
L = [a,b]
Yes (0.00s cpu)

4
语法比NU(1987年)或MU要古老得多。 - false
@false: 让我很好奇。你能指出一些容易获取的参考资料吗?我从未在其他任何地方看到它被用作中缀运算符。例如,我记不得在C&M中看到过它。 - twinterer
我在我的回答中添加了最显著的参考。 - false

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接