使谓词可逆
Make a predicate reversible
我是序言新手;我来自结构化编程背景,这一点很明显:)
我正在构建一个涉及反转数字的序言查询;例如。 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 做一些它不应该做的事情(是否应该理解为只使用数字作为第一个参数,变量作为第二个参数)?
或者是否有一种简单/直接的方法将变量作为第一个参数处理?
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) 可逆性是 undecidable 属性 因为 Prolog 是图灵完备的; (b) Prolog 的特征之一是体原子的计算从左到右。否则评估某些程序将需要很长时间。有逻辑引擎可以按任意顺序执行此操作,因此将成功完成此任务。
如果 predicate/2
是可交换的,可以推广的解决方案是以下模式:
predicate(X,Y) :-
predicate1(X,A),
predicate2(A,B),
% ...
predicaten(C,Y).
predicate(X,Y) :-
predicate(Y,X).
但是你不能简单地将最后一个子句添加到理论中,因为它可以无限循环。
很高兴看到有人也担心在绑定参数集中定义没有限制的灵活规则。
如果使用支持协程 和 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 中的思想是定义关系。你的名字 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 基本上声明:
This goal is too general that I can make a good answer. Maybe there are infinitely many solutions, maybe not. I know not. But at least I indicate this by issuing an error. To remove this error you need to instantiate the arguments a bit more.
所以 Prolog 产生的答案根本那么糟糕!事实上,产生一个干净的错误比错误地失败要好得多。通常,Prolog 的错误通常是对您可能遇到的语义问题的非常有用的提示。见 all error classes 如何。
协程
正如其他答案所建议的那样,协程,使用 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)
,这只有在我们产生无限多的答案时才是正确的 - 但是,正如我们所见 - 这会花费太多时间。
协程在 80 年代初看起来是一条非常有前途的道路。然而,它从未真正成为一种通用的编程方法。大多数时候你会遇到太多的困难,这只是一种痛苦,甚至比实例化错误更难处理。
然而,这种发展的更有前途的后代是制约因素。在那里,机制定义得更清晰。出于实际目的,程序员将只使用现有的库,如 CLPFD、CLPQ 或 CHR。实现您自己的库本身就是一个非常重要的项目。事实上,甚至可以使用 library(clpfd)
提供 num_reversed/2
的实现,即将关系限制为整数情况。
模式相关条件
传统上,许多此类问题都是通过显式测试实例化来解决的。像 when/2
- other type test predicates lead easily to errors as exemplified by .
中的条件一样,只使用 nonvar/1
和 ground/1
执行此操作是一种很好的风格
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/IEC13211-1:1995有如下template and mode subclause:
8.16.7.2 Template and modes
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 这种重写是必要的,因为在许多序言中 reverse/2
只有在第一个参数已知时才会终止。在 SWI 中,由于一些特殊的低效率,这种重写是必要的。
这个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 校正后,ground/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 有问题:绑定到反向的列表 'fired' 太早了,当头被绑定时.. .太快了!
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).
抛开尾随零变成前导零的问题,它似乎不应该比这样的事情复杂得多(通过处理负数变得更加复杂):
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
的参数之一。
我是序言新手;我来自结构化编程背景,这一点很明显:)
我正在构建一个涉及反转数字的序言查询;例如。 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 做一些它不应该做的事情(是否应该理解为只使用数字作为第一个参数,变量作为第二个参数)?
或者是否有一种简单/直接的方法将变量作为第一个参数处理?
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) 可逆性是 undecidable 属性 因为 Prolog 是图灵完备的; (b) Prolog 的特征之一是体原子的计算从左到右。否则评估某些程序将需要很长时间。有逻辑引擎可以按任意顺序执行此操作,因此将成功完成此任务。
如果 predicate/2
是可交换的,可以推广的解决方案是以下模式:
predicate(X,Y) :-
predicate1(X,A),
predicate2(A,B),
% ...
predicaten(C,Y).
predicate(X,Y) :-
predicate(Y,X).
但是你不能简单地将最后一个子句添加到理论中,因为它可以无限循环。
很高兴看到有人也担心在绑定参数集中定义没有限制的灵活规则。
如果使用支持协程 和 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 中的思想是定义关系。你的名字 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 基本上声明:
This goal is too general that I can make a good answer. Maybe there are infinitely many solutions, maybe not. I know not. But at least I indicate this by issuing an error. To remove this error you need to instantiate the arguments a bit more.
所以 Prolog 产生的答案根本那么糟糕!事实上,产生一个干净的错误比错误地失败要好得多。通常,Prolog 的错误通常是对您可能遇到的语义问题的非常有用的提示。见 all error classes 如何。
协程
正如其他答案所建议的那样,协程,使用 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)
,这只有在我们产生无限多的答案时才是正确的 - 但是,正如我们所见 - 这会花费太多时间。
协程在 80 年代初看起来是一条非常有前途的道路。然而,它从未真正成为一种通用的编程方法。大多数时候你会遇到太多的困难,这只是一种痛苦,甚至比实例化错误更难处理。
然而,这种发展的更有前途的后代是制约因素。在那里,机制定义得更清晰。出于实际目的,程序员将只使用现有的库,如 CLPFD、CLPQ 或 CHR。实现您自己的库本身就是一个非常重要的项目。事实上,甚至可以使用 library(clpfd)
提供 num_reversed/2
的实现,即将关系限制为整数情况。
模式相关条件
传统上,许多此类问题都是通过显式测试实例化来解决的。像 when/2
- other type test predicates lead easily to errors as exemplified by
nonvar/1
和 ground/1
执行此操作是一种很好的风格
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/IEC13211-1:1995有如下template and mode subclause:
8.16.7.2 Template and modes
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 这种重写是必要的,因为在许多序言中 reverse/2
只有在第一个参数已知时才会终止。在 SWI 中,由于一些特殊的低效率,这种重写是必要的。
这个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 校正后,ground/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 有问题:绑定到反向的列表 'fired' 太早了,当头被绑定时.. .太快了!
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).
抛开尾随零变成前导零的问题,它似乎不应该比这样的事情复杂得多(通过处理负数变得更加复杂):
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
的参数之一。