我应该通过抛出实例化错误来强制执行模式声明吗?
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).
我一直在编写一些代码,其中我的谓词在某些模式下使用时要么不会终止,要么给出不正确的解决方案。
这是一个例子:
%! 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).