使谓词可逆

时间:2015-05-31 01:41:44

标签: prolog

我是初学者;我来自结构化编程背景,很明显:):

我正在构建一个涉及撤消数字的序言查询;例如。 reverse_num(123,X)会产生X = 321。我提出了以下定义,但它仅在我提供数字作为第一个参数时才有效。

reverse_num(Num, Revnum) :-
  number_chars(Num, Atoms),
  reverse(Revatoms, Atoms),
  number_chars(Reversed, Revatoms),
  Reversed = Revnum.

number_chars/2谓词不喜欢未经证实的变量:reverse_num(X,123)(我希望X321)。

我是否努力让reverse_num做一些它不应该做的事情(应该理解为仅使用数字作为第一个参数而变量作为第二个参数)?

或者是否有一种简单/直接的方法来处理变量作为第一个参数?

5 个答案:

答案 0 :(得分:4)

关系命名

在开始编码之前,让我们退后一步。毕竟,Prolog的想法是定义关系。您的姓名reverse_num/2反而建议采取某些行动,num_reversed/2可能是更好的名称。

确定关系

你的定义并不是那么糟糕,让我把它重写为 1

num_reversed(Num, Reversed) :-
   number_chars(Num, Chars),
   reverse(Chars, Revchars),
   number_chars(Reversed, Revchars).

?- num_reversed(123,X).
X = 321.

?- num_reversed(1230,X).
X = 321.

?- num_reversed(12300,X).
X = 321.

你看到了这种模式吗?所有数字N*10^I都有相同的结果!

现在,让我们再问一下:

?- num_reversed(Num, 321).
ERROR: number_chars/2: Arguments are not sufficiently instantiated
嗯,我们期待什么?实际上,我们希望打印所有123*10^I。这是无数的解决方案。因此,如果正确回答,则需要无限多个解决方案进行打印。如果我们直接打印它们,那将占用我们所有宇宙的一生,甚至更多!

正是出于这个原因,Prolog产生了一个实例化错误。通过这个,Prolog基本上说:

  

这个目标过于笼统,我可以做出一个好的答案。也许有无数的解决方案,也许不是。我不知道。但至少我通过发出错误来表明这一点。要删除此错误,您需要更多地实例化参数。

所以Prolog产生的答案根本不是那么糟糕!实际上,产生干净的错误要比错误地失败要好得多。一般来说,Prolog的错误通常是您可能遇到的语义问题的一个非常有用的提示。请参阅all error classes how。

Coroutining

如同其他建议一样,使用when/2进行协同处理可能会解决此问题。但是,协同处理本身存在许多语义问题。由于与包含检查有关的许多问题,像XSB这样的系统不提供它。与其兼容的实现会出乎意料地效率低下。

但是为了这一点,我们可以通过查询它来使我们的定义更加通用

 ?- when(nonvar(Num), num_reversed(Num, Reversed)).
 when(nonvar(Num), num_reversed(Num, Reversed)).

现在我们回复完全是我们输入的查询的答案。这也称为挣扎。所以 是一种以紧凑的方式无限表示解决方案的方法!然而,这是一个相当高的代价:你不再知道是否存在解决方案。想一想:

?- when(nonvar(Num), num_reversed(Num, -1)).
when(nonvar(Num), num_reversed(Num, -1)).

其他人建议等待nonvar(Reversed),如果我们会产生无限多的答案,这只会是正确的 - 但是,正如我们所看到的 - 这只需要太多时间。

Coroutining在20世纪80年代初看起来是一条非常有前景的道路。但是,它从未真正成为一种通用的编程方法。大多数时候你会得到太多的挣扎,这只是一种痛苦,甚至比实例化错误更难处理。

然而,这种发展的更有希望的后代是制约因素。在那里,机制更加清晰。出于实际目的,程序员只会使用现有的库,如CLPFD,CLPQ或CHR。实现自己的库本身就是一个非常重要的项目。实际上甚至可以使用num_reversed/2提供library(clpfd)的实现,即限制与整数情况的关系。

模式相关条件

传统上,通过明确地测试实例化来解决许多这样的问题。如when/2中的条件nonvar/1ground/1专门执行此操作是一种很好的风格 - 其他类型测试谓词很容易导致错误,例如another answer

num_reversed(Num, Reversed) :-
   (  nonvar(Num)
   -> original_num_reversed(Num, Reversed)
   ;  original_num_reversed(Reversed, Base),
      (  Base =:= 0
      -> Num is 0
      ;  length(_, I),
         Num is Base*10^I
      )
   ).

对于使用基数为2的浮点数,上面的代码很快就会中断,而对于基数为10,上面的代码会稍后中断。实际上,对于经典的基数2浮点数,关系本身没有多大意义。

关于number_chars/2的定义,ISO / IEC 13211-1:1995具有以下template and mode subclause

  

8.16.7.2模板和模式

     

number_chars(+number, ?character_list)
  number_chars(-number, +character_list)

第一种情况是第一个参数被实例化(因此nonvar)。第二种情况,第一个参数没有实例化。在这种情况下,必须实例化第二个参数。

但请注意,由于非常相似的问题,number_chars/2不是关系。例如,Chs = ['0','0'], number_chars(0, Chs)成功,而number_chars(0, Chs), Chs = ['0','0']失败。

非常精细的打印

1这种重写是必要的,因为在许多Prolog中reverse/2只有在第一个参数已知时才会终止。在SWI中,由于某些特殊的低效率,这种重写是必要的。

答案 1 :(得分:3)

number_chars/2谓词具有签名:

number_chars(?Number, ?CharList) 

但是,虽然签名未完全指定,但至少必须实例化NumberCharList。这就是错误发生的地方。

如果你打电话:

reverse_num(Num,123)

您将在此时调用number_chars/2两个未实例,因此谓词会出错。

问题的一个不太好的解决方案是询问NumRevNumnumber/2。您可以通过编写两个版本来完成此操作。它还会过滤其他调用,例如reverse_num(f(a),b)等:

reverse_num(Num,Revnum) :-
    \+ number(Num),
    \+ number(Revnum),
    throw(error(instantiation_error, _)).

reverse_num(Num, Revnum) :-
    ground(Num),
    number(Num),
    !,
    number_chars(Num, Atoms),
    reverse(Revatoms, Atoms),
    number_chars(Revnum, Revatoms).

reverse_num(Num, Revnum) :-
    ground(Revnum),
    number(Revnum),
    reverse_num(Revnum,Num).

或者你可以使用两个非全局(例如reverse_num(X,Y).)实例化错误而不是false,因为@false说:

reverse_num(Num,Revnum) :-
    \+ number(Num),
    \+ number(Revnum),
    !,
    throw(error(instantiation_error, _)).

reverse_num(Num, Revnum) :-
    number(Num),
    !,
    number_chars(Num, Atoms),
    reverse(Revatoms, Atoms),
    number_chars(Revnum, Revatoms).

reverse_num(Num, Revnum) :-
    reverse_num(Revnum,Num).

剪切(!)在行为上并不是必需的,但会稍微提高性能。我并不是这个实现的忠实粉丝,但是Prolog并不能总是完全使谓词可逆,因为(a)可逆性是一个不可判定的属性,因为Prolog是Turing完成的; (b)Prolog的一个特征是身体原子被评估为从左到右。否则评估一些程序需要很长时间。有些逻辑引擎可以按任意顺序执行此操作,因此可以成功执行此任务。

如果predicate/2是可交换的,可以推广的解决方案是以下模式:

predicate(X,Y) :-
    predicate1(X,A),
    predicate2(A,B),
    % ...
    predicaten(C,Y).
predicate(X,Y) :-
    predicate(Y,X).

但是你不能简单地将最后一个句子添加到理论中,因为它可以无限循环。

答案 2 :(得分:2)

很高兴看到有人也担心定义灵活的规则,在绑定参数集中没有限制。

如果使用支持与 when/2内置谓词(例如SICStus Prolog,SWI-Prolog或YAP)进行核对的Prolog系统,请尝试:

reverse_num(Num, Reversed) :-
  when( ( ground(Num); ground(Atoms) ), number_chars(Num, Atoms) ),
  when( ( ground(Reversed); ground(Revatoms) ), number_chars(Reversed, Revatoms) ),
  reverse(Atoms , Revatoms).

给出:

?- reverse_num( 123, X ).
X = 321.

?- reverse_num( X, 123 ).
X = 321 .

(感谢提供论文答案的人:Prolog: missing feature?

答案 3 :(得分:1)

SWISH session显示了我的回答。

然后我回到这里,发现我在@ PasabaPorAqui'情绪(+1),但我没有做对。

但是,这样一个有趣的话题:注意连接模式是多么规则。

reverse_num(X, Y) :-
    when((nonvar(Xs);nonvar(Ys)), reverse(Xs, Ys)),
    when((nonvar(X) ;nonvar(Xs)), atomic_chars(X, Xs)),
    when((nonvar(Y) ;nonvar(Ys)), atomic_chars(Y, Ys)).

所以,我们可以用一种简单的方式进行推广(在考虑PasabaPorAqui校正后,地面/ 1它是关键):

% generalized... thanks Pasaba Por Aqui
:- meta_predicate when_2(0).
when_2(P) :-
    strip_module(P,_,Q),
    Q =.. [_,A0,A1],
    when((ground(A0);ground(A1)), P).

reverse_num(X, Y) :-
    maplist(when_2, [reverse(Xs, Ys), atomic_chars(X, Xs), atomic_chars(Y, Ys)]).

我想我理解为什么 nonvar / 1是有问题的:列出了反向获取的列表'太早了,只是头部受到约束......太快了!

maplist / 2并不是必需的:我们可以手写

reverse_num(X, Y) :-
    when_2(reverse(Xs, Ys)),
    when_2(atomic_chars(X, Xs)),
    when_2(atomic_chars(Y, Ys)).

这似乎是术语重写的理想应用......您如何看待-:-?实现我们可以编写像

这样的双向代码
reverse_num(X, Y) -:-
    reverse(Xs, Ys),
    atomic_chars(X, Xs),
    atomic_chars(Y, Ys).

编辑 SWISH可能不是' term_rewrite'友好......所以这是一个较低级别的方法:

:- op(900, xfy, ++).
A ++ B ++ C :- when_2(A), B ++ C.
A ++ B :- when_2(A), when_2(B).

reverse_num(X, Y) :- 
    reverse(Xs, Ys) ++ atomic_chars(X, Xs) ++ atomic_chars(Y, Ys).

答案 4 :(得分:0)

撇开尾随零的问题变成前导零,看起来它应该比这样的事情复杂得多(通过处理负数使得更复杂):

reverse_number(X,Y) :- number(X) , ! , rev(X,Y) .
reverse_number(X,Y) :- number(Y) , ! , rev(Y,X) .

rev(N,R) :-
  N < 0 ,
  ! ,
  A is abs(N) ,
  rev(A,T) ,
  R is - T
  .
rev(N,R) :-
  number_chars(N,Ns) ,
  reverse(Ns,Rs) ,
  number_chars(R,Rs)
  .

请注意,这确实需要至少实现reverse_number/2的一个参数。