我是初学者;我来自结构化编程背景,很明显:):
我正在构建一个涉及撤消数字的序言查询;例如。 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)
(我希望X
为321
)。
我是否努力让reverse_num做一些它不应该做的事情(应该理解为仅使用数字作为第一个参数而变量作为第二个参数)?
或者是否有一种简单/直接的方法来处理变量作为第一个参数?
答案 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。
如同其他建议一样,使用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/1
和ground/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)
但是,虽然签名未完全指定,但至少必须实例化Number
或CharList
。这就是错误发生的地方。
如果你打电话:
reverse_num(Num,123)
您将在此时调用number_chars/2
两个未实例,因此谓词会出错。
问题的一个不太好的解决方案是询问Num
或RevNum
是number/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
的一个参数。