if_/3有什么用?

Fat*_*ize 30 if-statement prolog logical-purity

该谓词if_/3似乎在Stack Overflow的Prolog部分的少数主要贡献者中相当受欢迎.

这个谓词是这样实现的,由@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,_))
   ).
Run Code Online (Sandbox Code Playgroud)

但是,我一直无法找到这个谓词的作用的清晰,简单和简洁的解释,以及它与Prolog的经典if-then-else结构相比有什么用处if -> then ; else.

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

mat*_*mat 20

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

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

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

悲剧如下:

  • 对于实例化列表,模式匹配可以将第一个子句与其余两个子句区分开,但是它不能区分第二个子句和最后一个子句,因为它们具有'.'(_, _)作为其第一个参数的主要函子和arity.
  • 最后两个条款适用的条件显然是相互排斥的.
  • 因此,当一切都已知时,我们希望获得一个有效的,确定性的谓词,它不会留下选择点,理想情况下甚至不会创建选择点.
  • 但是,只要并非一切都可以安全地确定,我们希望从回溯中获益以查看所有解决方案,因此我们无法承担任何条款.

总之,现有的结构和语言特征在某种程度上都不足以表达经常在实践中出现的模式.因此,几十年来,似乎有必要  妥协.并且你可以很好地猜测Prolog社区中"妥协"通常会走向哪个方向:几乎无一例外,如果有疑问,就会牺牲正确性来提高效率.毕竟,只要你的节目很快,谁会关心正确的结果呢?因此,在发明之前if_/3,这经常被错误地写成:

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, ...)).

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

  • 确定性,如果一切都可以安全地决定
  • 高效,它甚至不创建点的选择
  • 完成后你永远不会错误地提交到一个特定的分支.

这个价格相当实惠:正如鲍里斯在评论中提到的那样,你需要实现一个具体化.我现在对此有一些经验,并且通过一些练习发现它相当容易.

每个人都好消息:在许多情况下,condition形式(=)/2,或者(#=)/2,第一个甚至是免费的.library(reif)

有关更多信息,请参阅Ulrich Neumerkel和Stefan Kral 索引dif/2!

  • 从这个答案得到的结论是正确的,应该总是使用`if_`而不是`if - >然后; else`(不包括特定情况),类似于应该如何使用`CLP(FD)`而不是低级算术(不包括特定情况)? (2认同)
  • 我会这样说:`if - > then; 在几乎所有情况下,else`导致**错误的**程序.使用这种非逻辑构造的唯一原因是使程序更有效,通常以正确的代价为代价.另一方面,`if_/3`*以正确的价格将*正确性与可接受的效率相结合:它需要一个具体化(通常是`(=)/ 3`,已经包含在库中)和一点点学习新结构.始终,最好的方法是尽可能使用模式匹配.如果那是不可能的,`if_/3`是*非常好的选择! (2认同)
  • @Fatalize:请阅读[第2节](https://arxiv.org/abs/1607.01590),其中称为"Prolog的if-then-else的声明性限制",以回答有关传统if-then-else的问题. (2认同)

小智 10

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

我将使用该库reif这里.所以,这是我的程序的完整代码:

:- 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]
                )
        ).
Run Code Online (Sandbox Code Playgroud)

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

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

有了这个,我可以在领先的as和休息中拆分列表:

?- 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 = [] .
Run Code Online (Sandbox Code Playgroud)

你怎么能摆脱领先0的例子:

?- pred_prefix([X, B]>>(=(0, X, B)), [0,0,1,2,0,3], _, F).
F = [1, 2, 0, 3].
Run Code Online (Sandbox Code Playgroud)

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

drop_leading_zeros([], []).
drop_leading_zeros([X|Xs], Rest) :-
    if_(=(0, X), drop_leading_zeros(Xs, Rest), [X|Xs] = Rest).
Run Code Online (Sandbox Code Playgroud)

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

如果你必须这样做没有 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
    ).
Run Code Online (Sandbox Code Playgroud)

在这里,我们假设在=/3没有选择点的情况下确实总能成功,并且T将始终是true或者false.

而且,如果我们没有=/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
    ).
Run Code Online (Sandbox Code Playgroud)

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

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