问题描述
我一直在研究某些谓词,它们在某些模式下使用时不会终止或给出错误的解决方案。
这里是一个例子:
%! 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,3,3],[1,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,Acc).
% This is great.
?- list_without_duplicates([1,X).
X = [1,3] ;
false.
% This is not great.
list_without_duplicates_(X,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],list_without_duplicates_star(Xs,Acc).
list_without_duplicates_star([X|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,[4,5,6]).
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [11] apply:maplist_([1,2|...],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
的建议使用@false。事实上,如果您仅查看@false资料中的链接,就可以在SO上找到一个不错的解决方案。
如果第一个参数未实例化,我最好抛出一个错误吗?
您的情况不多。实际上,您遇到的无法终止很烦人且浪费资源,但至少不是不正确的。我会更担心以下情况:
?- Y = b,list_without_duplicates([a,Y],[a,b]).
Y = b
; false. % inefficiency
?- list_without_duplicates([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)
).
考虑使用iwhen/2
隐藏详细信息;并轻松升级到相关程序:只需删除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,type_error(call(user:memberd_t(A,B),2,boolean,D)))
; throw(error(instantiation_error,instantiation_error(call(user:memberd_t(A,2)))
),list_nub(B,E).