if_ / 3有什么用?

时间:2016-10-03 13:54:45

标签: if-statement prolog logical-purity

在Stack Overflow的Prolog部分中,谓词 if_/3 似乎是fairly popular中的少数主要贡献者。

这个谓词是这样实现的,由@false提供:

if_(If_1, Then_0, Else_0) :-
   call(If_1, T),
   (  T == true -> call(Then_0)
   ;  T == false -> call(Else_0)
   ;  nonvar(T) -> throw(error(type_error(boolean,T),_))
   ;  /* var(T) */ throw(error(instantiation_error,_))
   ).

但是,我无法找到关于此谓词的作用的清晰,简单和简洁的解释,以及与谓词相比的用途。 Prolog if -> then ; else的经典if-then-else结构。

我发现的大多数链接直接使用这个谓词,并且很少解释为什么它被使用,Prolog中的非专家可以轻松理解。

2 个答案:

答案 0 :(得分:20)

在老式的Prolog代码中,以下模式经常出现:

predicate([], ...).
predicate([L|Ls], ...) :-
        condition(L),
        then(Ls, ...).
predicate([L|Ls], ...) :-
        \+ condition(L),
        else(Ls, ...).

我在这里使用列表作为发生这种情况的例子(参见例如include/3exclude/3等),尽管模式当然也发生在其他地方。

悲剧如下:

  • 对于实例化列表,模式匹配可以将第一个子句与其余两个子句区分开来,但它无法区分第二个子句与最后一个子句,因为它们具有{ {1}}作为他们第一个论点的主要函子和主体。
  • 最后两个条款适用的条件显然是互相排斥
  • 因此,当一切都已知时,我们希望获得一个有效的,确定性的谓词,不会留下选择点,理想情况下甚至不会创建< / em>选择点。
  • 然而,只要不是一切都可以安全确定,我们希望从回溯中受益,看看所有解决方案,所以我们不能承诺遵守任何一个条款。

总之,现有的结构和语言特征在某种程度上都不足以表达经常在实践中出现的模式。因此,几十年来,似乎有必要妥协。你可以很好地猜测Prolog社区中“妥协”的方向:几乎无一例外,如果有疑问,正确性会因效率而牺牲。毕竟,只要你的程序很快,谁会关心正确的结果,对吧?因此,在'.'(_, _)的发明之前,这经常是错误地写成:

predicate([], ...).
predicate([L|Ls], ...) :-
        (    condition(L) ->
             then(Ls, ...).
        ;    else(Ls, ...).
        )

错误当然是当 元素被充分实例化时,这可能错误地提交到一个分支,即使两种替代方案在逻辑上都是可行的。出于这个原因,使用if-then-else几乎总是声明性地错误,并且由于它违反了我们对纯Prolog程序期望的最基本属性而大量采用声明式调试方法。

使用if_/3,您可以将其写为:

predicate([], ...).
predicate([L|Ls], ...) :-
        if_(condition(L),
            then(Ls, ...),
            else(Ls, ...)).

保留所有理想的方面。这是:

  • 确定性如果一切都可以安全地决定
  • 高效,因为它甚至没有创建选择点
  • 完成,因为您从未错误地提交到某个特定分支。

价格这是相当实惠的:正如Boris在评论中提到的,您需要实施具体化。我现在对此有一些经验,并且通过一些练习发现它很容易。

每个人都好消息:在很多情况下,if_/3的格式为condition(=)/2,第一个甚至附带library(reif) } 免费

有关更多信息,请参阅Ulrich Neumerkel和Stefan Kral的Indexing dif/2

答案 1 :(得分:10)

让我们尝试使用if_/3来解决一个简单的问题;例如,我将尝试在两个列表中对列表进行分区(在谓词p/2上排序):一个前缀,对于每个元素X,我们有p(X, true),其余(其中,如果列表在p/2上排序,我们将p(X, false)

我将使用库reif作为here。所以,这是我的程序的完整代码:

:- use_module(reif).

pred_prefix(Pred_1, List, L_true, L_false) :-
        pred_prefix_aux(List, Pred_1, L_true, L_false).

pred_prefix_aux([], _, [], []).
pred_prefix_aux([X|Xs], Pred_1, True, False) :-
        if_(    call(Pred_1, X),
                (       True = [X|True0],
                        pred_prefix_aux(Xs, Pred_1, True0, False)
                ),
                (       True = [],
                        False = [X|Xs]
                )
        ).

传递给此元谓词的谓词将采用两个参数:第一个是当前列表元素,第二个是truefalse。理想情况下,这个谓词总会成功,而不会留下选择点。

if_/2的第一个参数中,使用当前列表元素评估谓词;第二个论点是true时发生的事情;第三个参数是false时发生的事情。

有了这个,我可以在前导a和休息中分割一个列表:

?- pred_prefix([X, B]>>(=(a, X, B)), [a,a,b], T, F).
T = [a, a],
F = [b].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,c,d], T, F).
T = [],
F = [b, c, d].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,a], T, F).
T = [],
F = [b, a].

?- pred_prefix([X, B]>>(=(a, X, B)), List, T, F).
List = T, T = F, F = [] ;
List = T, T = [a],
F = [] ;
List = T, T = [a, a],
F = [] ;
List = T, T = [a, a, a],
F = [] .

如何摆脱领先的0,例如:

?- pred_prefix([X, B]>>(=(0, X, B)), [0,0,1,2,0,3], _, F).
F = [1, 2, 0, 3].

当然,这可以写得更简单:

drop_leading_zeros([], []).
drop_leading_zeros([X|Xs], Rest) :-
    if_(=(0, X), drop_leading_zeros(Xs, Rest), [X|Xs] = Rest).

我刚刚删除了所有不必要的参数。

如果您必须在没有 if_/3的情况下执行,则必须写下:

drop_leading_zeros_a([], []).
drop_leading_zeros_a([X|Xs], Rest) :-
    =(0, X, T),
    (   T == true -> drop_leading_zeros_a(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

在此,我们假设=/3确实总是在没有选择点的情况下成功,T将始终为truefalse

而且,如果我们也没有=/3,你会写:

drop_leading_zeros_full([], []).
drop_leading_zeros_full([X|Xs], Rest) :-
    (   X == 0 -> T = true
    ;   X \= 0 -> T = false
    ;   T = true, X = 0
    ;   T = false, dif(0, X)
    ),
    (   T == true -> drop_leading_zeros_full(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

这不太理想。但现在至少你可以在一个地方看到实际发生的事情。

PS:请仔细阅读代码和顶级互动。