我应该通过抛出实例化错误来强制执行模式声明吗?

Should I enforce mode declarations by throwing instantiation errors?

我一直在编写一些代码,其中我的谓词在某些模式下使用时要么不会终止,要么给出不正确的解决方案。

这是一个例子:

%!  list_without_duplicates(+List1, -List2) is det.
%
%   True if List2 contains all the elements of List1 but has
%   no duplicate elements. 
%
%   Ex: list_without_duplicates([1,1,2,2,3,3],[1,2,3]).

list_without_duplicates([],[]).
list_without_duplicates([X|Xs],[X|Acc]) :-
    \+ memberchk(X,Xs),
    list_without_duplicates(Xs,Acc).
list_without_duplicates([X|Xs],Acc) :-
    memberchk(X,Xs),
    list_without_duplicates(Xs,Acc).

% This is great.
?- list_without_duplicates([1,1,2,2,3,3],X).
X = [1, 2, 3] ; 
false.

% This is not great.
list_without_duplicates_(X,[1,2,3]).
ERROR: Stack limit (1.0Gb) exceeded
ERROR:   Stack sizes: local: 1Kb, global: 0.8Gb, trail: 0.1Mb
ERROR:   Stack depth: 16,586, last-call: 100%, Choice points: 5
...

所以我的问题是,如果第一个参数未实例化,我是否最好抛出错误?

list_without_duplicates(List1,List2) :-
    (  var(List1) 
    -> instantiation_error(List1)
    ;  list_without_duplicates_star(List1,List2)
    ).

list_without_duplicates_star([],[]).
list_without_duplicates_star([X|Xs],[X|Acc]) :-
    \+ memberchk(X,Xs),
    list_without_duplicates_star(Xs,Acc).
list_without_duplicates_star([X|Xs],Acc) :-
    memberchk(X,Xs),
    list_without_duplicates_star(Xs,Acc).

我一直在阅读一些 Prolog 库,例如 apply.pl,它在我的系统上位于 /usr/local/logic/lib/swipl/library/apply.pl。这是直接来自该库的代码。请注意,此处未提及任何实例化错误。

maplist(Goal, List1, List2) :-
    maplist_(List1, List2, Goal).

maplist_([], [], _).
maplist_([Elem1|Tail1], [Elem2|Tail2], Goal) :-
    call(Goal, Elem1, Elem2),
    maplist_(Tail1, Tail2, Goal).

然而,如果我像这样使用这个谓词,我会得到一个实例化错误:

?- use_module(library(apply)).
true.

?- apply:maplist(X,[1,2,3],[4,5,6]).
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [11] apply:maplist_([1,2|...],[4,5|...],apply:_5706)
ERROR:    [9] toplevel_call(user:apply: ...) at /usr/local/logic/lib/swipl/boot/toplevel.pl:1113
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

我不明白 Prolog 是怎么知道抛出这个错误的。

我不会直接输入你的 Prolog 代码,除非你绝对确定你别无选择。

使用内置插件,它们免费提供大量“类型检查”。将 call 与不可调用项一起使用就是一个例子。基本上所有的内置函数都会检查它们的参数,如果它们不这样做,我会认为这是一个错误并报告它。示例:

?- between(1, 3, foo).
?- succ(X, 0).
?- X = [_|X], length(X, N).
?- X is 3 - a.
?- X is 3 - A.
?- sort([a,b|c], Sorted).

换句话说,只要您找到合适的内置函数在您自己的代码中使用,您就不需要显式抛出。

如果您要检查参数,请继续使用 library(error)

“不重复”谓词是永恒的经典。您需要一个很好的理由 不为此使用sort/2。如果你确实使用了sort/2,你会立即得到一个错误:

?- sort(X, Y).
ERROR: Arguments are not sufficiently instantiated

如果你决定自己编程,你还不如去兔子洞用if_/3 as suggested by @false。事实上,如果您只是浏览 @false 个人资料中的链接,您可能会在 SO 上找到一个奇特的解决方案。

am I better off throwing an error if the first argument is not instantiated?

你的情况不多。事实上,您遇到的非终止很烦人并且浪费资源,但至少不是不正确的。我会更关心这样的情况:

?- Y = b, list_without_duplicates([a,Y],[a,b]). 
   Y = b
;  false.                         % inefficiency                         
?-        list_without_duplicates([a,Y],[a,b]).
   false.                         % incompleteness

在存在约束的情况下,情况会变得更糟。

作为一般的经验法则,每当您想根据实例进行辨别时,请测试实例化程度更高的模式。在您的情况下,不要使用 var/1 进行测试,而是使用 nonvar/1。这会将您的注意力集中在更安全的情况下。在您的情况下,您可能已经意识到仅 nonvar/1 是不够的。实际上,使用 ground/1:

list_without_duplicates(List1,List2) :-
    (  ground(List1) 
    -> list_without_duplicates_star(List1,List2)
    ; instantiation_error(List1)
    ).

考虑使用 隐藏细节;并轻松升级到协程:只需删除 i 即可使用 when/2.

一般来说,实例化错误是为了掩盖程序问题。其中一些与非终止相关,另一些有助于屏蔽不纯代码的非关系部分,如 memberchk/2.

那么问题来了,为什么一开始就写不纯的代码?更何况,如果它像你一样效率低下?使用 library(reif),您将获得一个干净、纯粹且非常有效的解决方案:

:- use_module(library(reif)).
list_nub([], []).
list_nub([X|Xs], Ys0) :-
   if_(memberd_t(X,Xs), Ys0 = Ys1, Ys0 = [X|Ys1]),
   list_nub(Xs, Ys1).

回答@gusbro 关于 SWI 性能的评论,这里是 SICStus Prolog 中的扩展(为了获得该列表,我声明 list_nub/2 动态)。扩展在 SWI 中应该看起来相似。

list_nub([], []).
list_nub([A|B], C) :-
        memberd_t(A, B, D),
        (   D==true ->
            C=E
        ;   D==false ->
            C=[A|E]
        ;   nonvar(D) ->
            throw(error(type_error(boolean,D),type_error(call(user:memberd_t(A,B),D),2,boolean,D)))
        ;   throw(error(instantiation_error,instantiation_error(call(user:memberd_t(A,B),D),2)))
        ),
        list_nub(B, E).