如何更优雅地编写这个DCG?
How to write this DCG more elegantly?
在玩 DCG 时遇到了以下问题:
我想解析并生成精确数量的空格 (" "
)。我简单地这样做的简单方法:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ N > 0, N is M+1 },
" ",
trivial_nat_space(M).
非常失败,因为 N
和 M
的实例化不充分,具体取决于我是否这样做
?- String=" ", my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])
或
?- Anz_Spaces=3,my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])
其中(为方便起见)
my_string_codes(S,C) :-
when((ground(S);ground(C)), string_codes(S,C)).
为了寻找解决问题的好方法,我制作了一个依赖于自定义 nats 的版本:
z.
s(z).
s(s(O)) :-
s(O).
nat_num(S,C) :-
when((ground(S);ground(C)),nat_num_(S,C)).
nat_num_(z,0) :- !.
nat_num_(s(N),X) :-
nonvar(X),
!,
X > 0,
Y is X-1,
nat_num_(N,Y).
nat_num_(s(N),X) :-
var(X),
nat_num_(N,Y),
X is Y+1.
n_space(z) --> [].
n_space(s(N)) -->
" ",
n_space(N).
我想避免这种情况,因为内置数字中已经存在自然数的附加编码。
还有这个:
nat_space(0) --> [].
nat_space(N) -->
{ var(N) },
" ",
nat_space(M),
{ N is M+1 }.
nat_space(M) -->
{ nonvar(M), M>0 },
" ",
{ N is M-1 },
nat_space(N).
哪个工作正常:
?- Anz_Spaces=3,my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
Anz_Spaces = 3,
String = " ",
Codes = [32, 32, 32].
?- String=" ",my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
String = " ",
Codes = [32, 32, 32],
Anz_Spaces = 3.
但是 nat_spaces
的编码(在我看来)远非如此:它依赖于元谓词来强制执行特定的执行顺序,并且(更严重的是):如果解析器比只是 " "
,逻辑必须在单独的 DCG predicate/rule 中定义,导致单个 parser/generator 的逻辑被分成两个定义(分开的一个和一个执行正确的执行顺序)。
这是解决此类问题的 canonical/standard 方法还是我现在缺少更通用、更优雅的解决方案?
附加信息:
我正在为 x86_64-linux 使用 SWI-Prolog 版本 8.3.9
使用 :- [library(dcg/basics)]
并且在启动运行时时没有其他参数。我也不使用定义在文件中设置任何设置。
对于您的具体示例,您可以使用 CLP(fd) 以能够以两种方式使用 DCG:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ N #> 0, M #= N-1 },
" ",
trivial_nat_space(M).
在以下示例运行中,我将使用反引号 (`
) 来使用编码字符串:
?- phrase(trivial_nat_space(Anz_Spaces), ` `, []).
Anz_Spaces = 3 ;
false.
?- phrase(trivial_nat_space(3), Spaces, []).
Spaces = [32, 32, 32] ;
false.
?- phrase(trivial_nat_space(N), Spaces, []).
N = 0,
Spaces = [] ;
N = 1,
Spaces = [32] ;
N = 2,
Spaces = [32, 32] ;
N = 3,
Spaces = [32, 32, 32]
...
坦率地说,您最初的定义并没有非常失败。不,它不会失败。对于最一般的查询,它会生成一个解决方案,
?- phrase(trivial_nat_space(0), Cs).
Cs = [] % pure perfect logic!
; false.
?- phrase(trivial_nat_space(-1), Cs).
false. % it's right to be false!
?- phrase(trivial_nat_space(1), Cs).
caught: error(instantiation_error,(is)/2) % er...
?- phrase(trivial_nat_space(N), Cs). % most general query
Cs = [], N = 0 % again, pure logic
; caught: error(instantiation_error,(is)/2) % mmh...
... 否则会出现实例化错误。实例化错误并不是可能发生的最糟糕的情况。他们清楚而诚实地声明,在我们继续之前,必须提供更多信息(=实例化)。这 比假装一切都很好 要好得多。将询问更多信息的职员想象成产生实例化错误。然后将其与仅使用一些大胆的默认假设填写 IRS 表格的表格进行比较1.
为了定位实例化错误的原因,我们将使用 failure-slice。因此,我将加入一些 false
目标以及一个额外的实例化以使其更容易:
trivial_nat_space(0) --> [], {false}.
trivial_nat_space(N) --> {N = 1},
{ N > 0, N is M+1, false },
" ",
trivial_nat_space(M).
?- phrase(trivial_nat_space(1), Cs).
caught: error(instantiation_error,(is)/2)
这是一个非常不正常的程序!而且它仍然会产生实例化错误。为了修复您的原始程序,我们必须修改剩余可见部分的某些内容。在 N is M+1
中,只有 M
会导致该错误。事实上,它是第一次在这里发生。我们可以将其替换为 M is N-1
以改进您的程序:
?- phrase(trivial_nat_space(1), Cs).
Cs = " " % see section double quotes
; false.
?- phrase(trivial_nat_space(2), Cs).
Cs = " "
; false.
?- phrase(trivial_nat_space(N), Cs).
Cs = [], N = 0
; caught: error(instantiation_error,(is)/2) % still ...
?- phrase(trivial_nat_space(N), " ").
caught: error(instantiation_error,(is)/2)
我们的程序现在至少在知道具体的空格数时才能运行。更好的是,我们还可以使用算术表达式!
?- phrase(trivial_nat_space(4*1), Cs). % let's indent by four spaces
Cs = " "
; false.
?- phrase(trivial_nat_space(4*2), Cs). % ... twice ...
Cs = " "
; false.
?- phrase(trivial_nat_space(4*0), Cs). % ... none?
false.
?- phrase(trivial_nat_space(0), Cs).
Cs = [] % here it works
; false.
所以 N
可能是一个算术整数表达式,并且它按预期工作,除了 0
必须按字面说明。这并不是一个很深的问题,没有违反代数性质。但它并不优雅。让我们记住这一点。
回到实例化错误。为了处理这些情况,我们需要一些方法来处理这个变量 N
。最简单的方法是使用 library(clpz)
或其在 SWI library(clpfd)
中的前身,如建议的那样 。是的,您可以手动执行此类操作,但因此您正在重复已投入该库的工作。出于性能原因,有时它可能有意义,但它会付出高昂的(错误缠身)代价。
所以让我们看看@gusbro 的解决方案,不要忘记添加
:- use_module(library(clpz)). % SICStus or Scryer
:- use_module(library(clpfd)). % SWI
?- phrase(trivial_nat_space(N), Cs).
Cs = [], N = 0
; Cs = " ", N = 1 % finally, logic!
; Cs = " ", N = 2
; Cs = " ", N = 3
; ...
?- phrase(trivial_nat_space(N), " ").
N = 2
; false.
?- N = 1+1, phrase(trivial_nat_space(N), " ").
N = 1+1 % nice like is/2
; false.
?- phrase(trivial_nat_space(N), " "), N = 1+1.
false. % and out, why?
直到上次查询为止,一切都很好。因此,带有算术表达式的扩展效果不是很好。实际上它归结为以下问题:
?- N = 1+1, N #= 2.
N = 1+1.
?- N #= 2, N = 1+1.
false.
在第一个查询中,我们求解整数方程 1+1 #= 2 成功,在第二个查询中,我们求解 N #= 2
成功 N = 2
然后我们尝试解决失败的2 = 1+1
。
换句话说,对一般算术表达式的扩展对于约束来说效果不佳。以前,实例化错误隐藏了问题。现在我们需要以某种方式划清界限。如上所述违反交换性不是一个好的选择2.
解决方案是显式分离表达式变量和整数变量,并坚持使用完全实例化的表达式。
?- N = 1+1, #N #= 2.
caught: error(type_error(integer,1+1),must_be/2)
?- #N #= 2, N = 1+1.
false.
?- assertz(clpz:monotonic).
true.
?- N #= 2, N = 1+1.
caught: error(instantiation_error,instantiation_error(unknown(_102),1))
所以现在@gusbro 的程序进行了一些细微的修改:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ #N #> 0, #M #= #N-1 },
" ",
trivial_nat_space(M).
double_quotes
既然你想要优雅的代码, to use as a 文本:字符列表。通过这种方式,您可以避免所有这些永远不会优雅的转换代码。在 Tau、Trealla 和 Scryer 等系统中,双引号项默认为 chars
。在 SWI 中像这样进行:
?- L = "ab", L = [_,_].
false.
?- phrase("ab","ab").
false.
?- set_prolog_flag(double_quotes, chars).
true.
?- L = "ab", L = [_,_].
L = [a, b].
?- phrase("ab","ab").
true.
与 library(double_quotes)
?- L = "ab", L = [_,_].
L = "ab".
语法
最后,关于一般的多向语法规则,还有一些需要注意的地方。想想一个谓词 text_ast/2
。对于一个抽象语法树,有无限的有效程序文本,它们都因布局文本等琐碎的用具而不同。因此,当只给出 AST 时,这个一般关系 一定不会 终止。所以你需要一个额外的参数来指示文本是否应该是规范的。
1 事实上,在 DEC10 Prolog 中,算术表达式中变量的默认假设是零值。 ISO Prolog 为这些情况定义了实例化错误。
2 在 SICStus 的原生库 clpfd 中,同样的问题出现在 ?- N = 1+1, call(N #= 2).
中。
在这种情况下,我们可以完全避免显式算术,让统一来完成工作:
:- set_prolog_flag(double_quotes, chars).
spaces --> "".
spaces --> " ", spaces.
n_spaces(N, Spaces) :-
length(Spaces, N),
phrase(spaces, Spaces).
?- n_spaces(N, S).
N = 0,
S = [] ;
N = 1,
S = [' '] ;
N = 2,
S = [' ', ' ']
?- n_spaces(2, S).
S = [' ', ' '].
?- n_spaces(N, " ").
N = 2.
我们在这里需要 double_quotes
标志,因为(至少在 SWI-Prolog 8.0.2 中)似乎字符串必须是 ground 或 var,不像列表可以有变量 entries/tails,所以我认为不可能将这种统一技术与 SWI-style 字符串一起使用:
?- string_length(String, Length).
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [8] string_length(_10886,_10888)
ERROR: [7] <user>
在玩 DCG 时遇到了以下问题:
我想解析并生成精确数量的空格 (" "
)。我简单地这样做的简单方法:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ N > 0, N is M+1 },
" ",
trivial_nat_space(M).
非常失败,因为 N
和 M
的实例化不充分,具体取决于我是否这样做
?- String=" ", my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])
或
?- Anz_Spaces=3,my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])
其中(为方便起见)
my_string_codes(S,C) :-
when((ground(S);ground(C)), string_codes(S,C)).
为了寻找解决问题的好方法,我制作了一个依赖于自定义 nats 的版本:
z.
s(z).
s(s(O)) :-
s(O).
nat_num(S,C) :-
when((ground(S);ground(C)),nat_num_(S,C)).
nat_num_(z,0) :- !.
nat_num_(s(N),X) :-
nonvar(X),
!,
X > 0,
Y is X-1,
nat_num_(N,Y).
nat_num_(s(N),X) :-
var(X),
nat_num_(N,Y),
X is Y+1.
n_space(z) --> [].
n_space(s(N)) -->
" ",
n_space(N).
我想避免这种情况,因为内置数字中已经存在自然数的附加编码。
还有这个:
nat_space(0) --> [].
nat_space(N) -->
{ var(N) },
" ",
nat_space(M),
{ N is M+1 }.
nat_space(M) -->
{ nonvar(M), M>0 },
" ",
{ N is M-1 },
nat_space(N).
哪个工作正常:
?- Anz_Spaces=3,my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
Anz_Spaces = 3,
String = " ",
Codes = [32, 32, 32].
?- String=" ",my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
String = " ",
Codes = [32, 32, 32],
Anz_Spaces = 3.
但是 nat_spaces
的编码(在我看来)远非如此:它依赖于元谓词来强制执行特定的执行顺序,并且(更严重的是):如果解析器比只是 " "
,逻辑必须在单独的 DCG predicate/rule 中定义,导致单个 parser/generator 的逻辑被分成两个定义(分开的一个和一个执行正确的执行顺序)。
这是解决此类问题的 canonical/standard 方法还是我现在缺少更通用、更优雅的解决方案?
附加信息:
我正在为 x86_64-linux 使用 SWI-Prolog 版本 8.3.9
使用 :- [library(dcg/basics)]
并且在启动运行时时没有其他参数。我也不使用定义在文件中设置任何设置。
对于您的具体示例,您可以使用 CLP(fd) 以能够以两种方式使用 DCG:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ N #> 0, M #= N-1 },
" ",
trivial_nat_space(M).
在以下示例运行中,我将使用反引号 (`
) 来使用编码字符串:
?- phrase(trivial_nat_space(Anz_Spaces), ` `, []).
Anz_Spaces = 3 ;
false.
?- phrase(trivial_nat_space(3), Spaces, []).
Spaces = [32, 32, 32] ;
false.
?- phrase(trivial_nat_space(N), Spaces, []).
N = 0,
Spaces = [] ;
N = 1,
Spaces = [32] ;
N = 2,
Spaces = [32, 32] ;
N = 3,
Spaces = [32, 32, 32]
...
坦率地说,您最初的定义并没有非常失败。不,它不会失败。对于最一般的查询,它会生成一个解决方案,
?- phrase(trivial_nat_space(0), Cs).
Cs = [] % pure perfect logic!
; false.
?- phrase(trivial_nat_space(-1), Cs).
false. % it's right to be false!
?- phrase(trivial_nat_space(1), Cs).
caught: error(instantiation_error,(is)/2) % er...
?- phrase(trivial_nat_space(N), Cs). % most general query
Cs = [], N = 0 % again, pure logic
; caught: error(instantiation_error,(is)/2) % mmh...
... 否则会出现实例化错误。实例化错误并不是可能发生的最糟糕的情况。他们清楚而诚实地声明,在我们继续之前,必须提供更多信息(=实例化)。这 比假装一切都很好 要好得多。将询问更多信息的职员想象成产生实例化错误。然后将其与仅使用一些大胆的默认假设填写 IRS 表格的表格进行比较1.
为了定位实例化错误的原因,我们将使用 failure-slice。因此,我将加入一些 false
目标以及一个额外的实例化以使其更容易:
trivial_nat_space(0) --> [], {false}. trivial_nat_space(N) --> {N = 1}, { N > 0, N is M+1, false }," ",trivial_nat_space(M). ?- phrase(trivial_nat_space(1), Cs). caught: error(instantiation_error,(is)/2)
这是一个非常不正常的程序!而且它仍然会产生实例化错误。为了修复您的原始程序,我们必须修改剩余可见部分的某些内容。在 N is M+1
中,只有 M
会导致该错误。事实上,它是第一次在这里发生。我们可以将其替换为 M is N-1
以改进您的程序:
?- phrase(trivial_nat_space(1), Cs).
Cs = " " % see section double quotes
; false.
?- phrase(trivial_nat_space(2), Cs).
Cs = " "
; false.
?- phrase(trivial_nat_space(N), Cs).
Cs = [], N = 0
; caught: error(instantiation_error,(is)/2) % still ...
?- phrase(trivial_nat_space(N), " ").
caught: error(instantiation_error,(is)/2)
我们的程序现在至少在知道具体的空格数时才能运行。更好的是,我们还可以使用算术表达式!
?- phrase(trivial_nat_space(4*1), Cs). % let's indent by four spaces
Cs = " "
; false.
?- phrase(trivial_nat_space(4*2), Cs). % ... twice ...
Cs = " "
; false.
?- phrase(trivial_nat_space(4*0), Cs). % ... none?
false.
?- phrase(trivial_nat_space(0), Cs).
Cs = [] % here it works
; false.
所以 N
可能是一个算术整数表达式,并且它按预期工作,除了 0
必须按字面说明。这并不是一个很深的问题,没有违反代数性质。但它并不优雅。让我们记住这一点。
回到实例化错误。为了处理这些情况,我们需要一些方法来处理这个变量 N
。最简单的方法是使用 library(clpz)
或其在 SWI library(clpfd)
中的前身,如建议的那样
所以让我们看看@gusbro 的解决方案,不要忘记添加
:- use_module(library(clpz)). % SICStus or Scryer
:- use_module(library(clpfd)). % SWI
?- phrase(trivial_nat_space(N), Cs).
Cs = [], N = 0
; Cs = " ", N = 1 % finally, logic!
; Cs = " ", N = 2
; Cs = " ", N = 3
; ...
?- phrase(trivial_nat_space(N), " ").
N = 2
; false.
?- N = 1+1, phrase(trivial_nat_space(N), " ").
N = 1+1 % nice like is/2
; false.
?- phrase(trivial_nat_space(N), " "), N = 1+1.
false. % and out, why?
直到上次查询为止,一切都很好。因此,带有算术表达式的扩展效果不是很好。实际上它归结为以下问题:
?- N = 1+1, N #= 2.
N = 1+1.
?- N #= 2, N = 1+1.
false.
在第一个查询中,我们求解整数方程 1+1 #= 2 成功,在第二个查询中,我们求解 N #= 2
成功 N = 2
然后我们尝试解决失败的2 = 1+1
。
换句话说,对一般算术表达式的扩展对于约束来说效果不佳。以前,实例化错误隐藏了问题。现在我们需要以某种方式划清界限。如上所述违反交换性不是一个好的选择2.
解决方案是显式分离表达式变量和整数变量,并坚持使用完全实例化的表达式。
?- N = 1+1, #N #= 2.
caught: error(type_error(integer,1+1),must_be/2)
?- #N #= 2, N = 1+1.
false.
?- assertz(clpz:monotonic).
true.
?- N #= 2, N = 1+1.
caught: error(instantiation_error,instantiation_error(unknown(_102),1))
所以现在@gusbro 的程序进行了一些细微的修改:
trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
{ #N #> 0, #M #= #N-1 },
" ",
trivial_nat_space(M).
double_quotes
既然你想要优雅的代码,chars
。在 SWI 中像这样进行:
?- L = "ab", L = [_,_].
false.
?- phrase("ab","ab").
false.
?- set_prolog_flag(double_quotes, chars).
true.
?- L = "ab", L = [_,_].
L = [a, b].
?- phrase("ab","ab").
true.
与 library(double_quotes)
?- L = "ab", L = [_,_].
L = "ab".
语法
最后,关于一般的多向语法规则,还有一些需要注意的地方。想想一个谓词 text_ast/2
。对于一个抽象语法树,有无限的有效程序文本,它们都因布局文本等琐碎的用具而不同。因此,当只给出 AST 时,这个一般关系 一定不会 终止。所以你需要一个额外的参数来指示文本是否应该是规范的。
1 事实上,在 DEC10 Prolog 中,算术表达式中变量的默认假设是零值。 ISO Prolog 为这些情况定义了实例化错误。
2 在 SICStus 的原生库 clpfd 中,同样的问题出现在 ?- N = 1+1, call(N #= 2).
中。
在这种情况下,我们可以完全避免显式算术,让统一来完成工作:
:- set_prolog_flag(double_quotes, chars).
spaces --> "".
spaces --> " ", spaces.
n_spaces(N, Spaces) :-
length(Spaces, N),
phrase(spaces, Spaces).
?- n_spaces(N, S).
N = 0,
S = [] ;
N = 1,
S = [' '] ;
N = 2,
S = [' ', ' ']
?- n_spaces(2, S).
S = [' ', ' '].
?- n_spaces(N, " ").
N = 2.
我们在这里需要 double_quotes
标志,因为(至少在 SWI-Prolog 8.0.2 中)似乎字符串必须是 ground 或 var,不像列表可以有变量 entries/tails,所以我认为不可能将这种统一技术与 SWI-style 字符串一起使用:
?- string_length(String, Length).
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [8] string_length(_10886,_10888)
ERROR: [7] <user>