if_/3 有什么用? [英] What use does if_/3 have?

查看:28
本文介绍了if_/3 有什么用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

谓词if_/3 在 Prolog 部分的少数主要贡献者中似乎相当受欢迎堆栈溢出.

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

if_(If_1, Then_0, Else_0) :-呼叫(If_1,T),( T == true -> call(Then_0);T == 假 ->呼叫(Else_0);非变量(T)->抛出(错误(类型错误(布尔,T),_));/* var(T) */throw(error(instantiation_error,_))).

然而,我一直无法找到一个清晰、简单和简洁的解释来说明这个谓词的作用,以及它与例如相比的用途.Prolog if -> 的经典 if-then-else 结构然后 ;否则.

我发现的大多数链接都直接使用了这个谓词,并且几乎没有解释为什么使用它,Prolog 中的非专家可以很容易地理解.

解决方案

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

<前>谓词([],...).谓词([L|Ls], ...) :-条件(L),然后(Ls,...).谓词([L|Ls], ...) :-+ 条件(L),否则(Ls,...).

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

悲剧如下:

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

总而言之,现有的结构和语言特性都在某种程度上无法表达实践中经常出现的模式.因此,几十年来,似乎有必要妥协.并且您可以很好地猜测 Prolog 社区中妥协"通常朝哪个方向发展:几乎无一例外,为了效率而牺牲了正确性,以防万一.毕竟,只要您的程序很快,谁会关心正确的结果,对吗?因此,在 if_/3 发明之前,这经常被错误地写成:

<前>谓词([],...).谓词([L|Ls], ...) :-( 条件(L) ->然后(Ls,...).;否则(Ls,...).)

错误当然是当元素没有充分实例化时,这可能错误地提交到一个分支,即使两种替代方案在逻辑上都是可能的.出于这个原因,使用 if-then-else 几乎总是声明性错误,并且由于它违反了我们期望从纯 Prolog 程序中获得的最基本的属性,因此在声明性调试方法方面存在巨大障碍.

<小时>

使用if_/3,你可以这样写:

<前>谓词([],...).谓词([L|Ls], ...) :-if_(条件(L),然后(Ls, ...),否则(Ls,...)).

并且保留所有令人满意的方面.这是:

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

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

大家好消息:在很多情况下,condition 的形式是 (=)/2(#=)/2,第一个甚至带有 library(reif) 免费.

有关详细信息,请参阅 Ulrich Neumerkel 的 Indexing dif/2 和Stefan Kral!

The predicate if_/3 seems to be fairly popular among the few main contributors in the Prolog part of Stack Overflow.

This predicate is implemented as such, courtesy of @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,_))
   ).

However, I have been unable to find a clear, simple, and concise explanation of what this predicate does, and what use it has compared to e.g. the classical if-then-else construct of Prolog if -> then ; else.

Most links I have found directly use this predicate and provide little explanation as to why it gets used, that a non-expert in Prolog could understand easily.

解决方案

In old-fashioned Prolog code, the following pattern arises rather frequently:

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

I am using lists here as an example where this occurs (see for example include/3, exclude/3 etc.), although the pattern of course also occurs elsewhere.

The tragic is the following:

  • For an instantiated list, pattern matching can distinguish the first clause from the remaining two, but it cannot distinguish the second one from the last one because they both have '.'(_, _) as the primary functor and arity of their first argument.
  • The conditions in which the last two clauses apply are obviously mutually exclusive.
  • Thus, when everything is known, we want to obtain an efficient, deterministic predicate that does not leave choice points, and ideally does not even create choice points.
  • However, as long as not everything can be safely determined, we want to benefit from backtracking to see all solutions, so we cannot afford to commit to either of the clauses.

In summary, the existing constructs and language features all fall short in some way to express a pattern that often occurs in practice. Therefore, for decades, it seemed necessary to compromise. And you can make a pretty good guess in which direction the "compromises" usually go in the Prolog community: Almost invariably, correctness is sacrificed for efficiency in case of doubt. After all, who cares about correct results as long as your programs are fast, right? Therefore, until the invention of if_/3, this was frequently wrongly written as:

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

The mistake in this is of course that when the elements are not sufficiently instantiated, then this may incorrectly commit to one branch even though both alternatives are logically possible. For this reason, using if-then-else is almost always declaratively wrong, and stands massively in the way of declarative debugging approaches due to its violation of the most elementary properties we expect from pure Prolog programs.


Using if_/3, you can write this as:

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

and retain all desirable aspects. This is:

  • deterministic if everything can be safely decided
  • efficient in that it does not even create choice points
  • complete in that you never incorrectly commit to one particular branch.

The price of this is rather affordable: As Boris mentioned in the comments, you need to implement a reification. I have now some experience with this and found it rather easy with some practice.

Good news everyone: In many cases, condition is of the form (=)/2, or (#=)/2, and the first even ships with library(reif) for free.

For more information, see Indexing dif/2 by Ulrich Neumerkel and Stefan Kral!

这篇关于if_/3 有什么用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆