【问题标题】:Should I enforce mode declarations by throwing instantiation errors?我应该通过抛出实例化错误来强制执行模式声明吗?
【发布时间】:2020-11-09 23:03:42
【问题描述】:

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

这是一个例子:

%!  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 是如何知道抛出这个错误的。

【问题讨论】:

  • 我愿意。使用must_be/2,对于缺少的类型,您可以使用has_type/2 添加类型。如果您需要更多详细信息,请联系SWI-Prolog forum,我知道您已经是其成员了。
  • 我相信maplist错误来自call的特殊处理,这需要它的目标参数被实例化和可调用。

标签: prolog instantiation-error


【解决方案1】:

如果第一个参数没有实例化,我最好抛出错误吗?

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

?- 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)
    ).

考虑使用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,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 w.r.t. list_nub.
  • @gusbro: listing(list_nub) 检查扩展是否正常
  • 我看到 if_/3 正在扩展。我试图在 swish 上运行它,但模块 reif 似乎在那里丢失了。 Here's the notebook,试试test1(10000, L)test2(10000, L)。在我的 SWI list_nub/2 的本地副本上慢两倍。
  • @gusbro: memberchk/2 是一个手写的内置。大约是等效 once(member(X,Xs)) 的两倍。
  • 好吧,我使用once(member(X,Xs)) 而不是memberchk(X,Xs) 再次运行了测试,现在对于OP 算法和list_nub,我得到了可比的运行时间~60 秒。与 list_nub 相比,使用 ->/3member/2 时,结果仍然快 25%。
【解决方案2】:

除非你绝对确定你别无选择,否则我不会直接投入你的 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

如果您决定自己编程,您不妨按照@false 的建议,钻研兔子洞并使用if_/3。事实上,如果您只是查看@false 个人资料中的链接,您可能会在 SO 上找到一个奇特的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-29
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-04
    相关资源
    最近更新 更多