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!
小智 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:请仔细阅读代码和顶级互动.