如果其中一个语句失败,为什么 Prolog 会进一步跳过递归中的所有 if 语句?

Why does Prolog skip all further if statements in recursion if one of them fails?

程序的重点是计算整数列表中所有偶数的总和。

    is_even(Q):- Q mod 2 =:= 0.  

    sum_even([],0).
    sum_even([A|L],X) :-  sum_even(L,X1) -> is_even(A) -> X is X1 + A .

只要is_even谓词成功就没有问题,它通常会返回计算总和。然而,当数字不是偶数并且 is_even 检查它时,它失败了,返回递归并且失败了随后的所有事情,甚至不再检查数字是否是偶数并且只是 returns 错误。在充满偶数的列表中,它按预期工作,它 returns 列表中所有数字的总和。 This here is the trace of the code

使用具有 tail-end 递归的累加器是最快的:

is_even(N) :-
    N mod 2 =:= 0.

sum_even(Lst, Sum) :-
    sum_even_(Lst, 0, Sum).

sum_even_([], Sum, Sum).
sum_even_([H|T], Upto, Sum) :-
    (   is_even(H) ->
        Upto1 is Upto + H
    ;   Upto1 = Upto
    ),
    sum_even_(T, Upto1, Sum).


sum_even_slow([], 0).
sum_even_slow([H|T], Sum) :-
    sum_even_slow(T, Sum0),
    (   is_even(H) ->
        Sum is Sum0 + H
    ;   Sum = Sum0
    ).

swi-prolog 中的性能比较:

?- numlist(1, 1_000_000, L), time(sum_even(L, Sum)).
% 4,000,002 inferences, 0.765 CPU in 0.759 seconds (101% CPU, 5228211 Lips)
Sum = 250000500000.

?- numlist(1, 1_000_000, L), time(sum_even_slow(L, Sum)).
% 4,000,001 inferences, 4.062 CPU in 4.023 seconds (101% CPU, 984755 Lips)
Sum = 250000500000.
add_even(X, Buffer, Sum) :-
    (0 =:= X mod 2 -> 
        Sum is Buffer + X
    ;   Sum = Buffer).

foldl(add_even, Nums, 0, Sum) is about as fast as brebs' tail recursion. Using SWISH Online一起使用:

?- numlist(1, 1_000_000, _L), time(sum_even(_L, Sum)).
4,000,002 inferences, 0.699 CPU in 0.699 seconds (100% CPU, 5724382 Lips)
Sum = 250000500000

?- numlist(1, 1_000_000, _L), time(foldl(add_even, _L, 0, Sum)).
4,000,002 inferences, 0.712 CPU in 0.712 seconds (100% CPU, 5621314 Lips)
Sum = 250000500000

(我很好奇,如果他们都精确地进行了 4,000,002 次推理,那么 sum_even 为何似乎获得了更高的 LIPS 吞吐量并且完成速度稍快?)

这是

的原因
is_even(Q):- Q mod 2 =:= 0.  

sum_even([],0).
sum_even([A|L],X) :- sum_even(L,X1) -> is_even(A) -> X is X1 + A .

当列表中有奇数时失败是因为 '->'/2,“隐含”运算符,是一个 soft cut。它消除了选择点。这意味着回溯到它失败,从而展开整个事情并使整个谓词失败。

您的代码大致等同于

sum_even([],0).
sum_even([A|L],X) :- sum_even(L,X1), !, is_even(A), !, X is X1 + A .

你需要

  • 消除 '->'/2 — ',' 运算符不必是 AND 运算符,并且
  • 提供一种替代方法来处理奇数项的情况。

例如,你可以这样说:

is_even( Q ) :- Q mod 2 =:= 0.  

sum_even( []    , 0 ) .
sum_even( [A|L] , X ) :-
  sum_even(L,X1),
  ( is_even(A) ->
      X is X1 + A
    ;
      X is X1
  ) .

或者像这样:

is_even( Q ) :- Q mod 2 =:= 0.  

sum_even( []    , 0 ) .
sum_even( [A|L] , X ) :-
  sum_even(L,X1),
  add_even(A,X1,X) .

add_even(X,Y,Z) :- is_even(X), !, Z is X + Y .
add_even(_,Z,Z) .

但最简单、快速的方法是使用带有额外状态的辅助谓词。给定一个足够长的列表,上面的谓词将死于堆栈溢出。这不会:Prolog内置了TRO(tail recursion optimization),它通过重用栈帧有效地将递归调用转化为迭代,因此它可以处理任何深度的递归:

sum_even( Xs , S ) :- sum_even(Xs,0,S) .

sum_even( []     , S , S ) .
sum_even( [X|Xs] , T , S ) :- is_even(X), !, T1 is T+X, sum_even(Xs,T1,S) .

is_even( Q ) :- Q mod 2 =:= 0.