微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何更优雅地编写这个 DCG? 双引号语法

如何解决如何更优雅地编写这个 DCG? 双引号语法

在玩 DCG 时遇到了以下问题:

我想解析并生成精确数量的空格 (" ")。我简单地这样做的微不足道的方法

trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
    { N > 0,N is M+1 }," ",trivial_nat_space(M).

非常失败,因为 NM 的实例化不足,这取决于我是否这样做

?- String="   ",my_string_codes(String,Codes),phrase(trivial_nat_space(Anz_Spaces),Codes,[])

?- Anz_Spaces=3,[])

哪里(为了方便)

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,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),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,once(phrase(nat_space(Anz_Spaces),[])).
Anz_Spaces = 3,String = "   ",Codes = [32,32,32].

?- String="   ",[])).
String = "   ",32],Anz_Spaces = 3.

然而,nat_spaces 的编码(在我看来)远不是很好:它依赖于元谓词来强制执行特定的执行序列,并且(更严重):如果解析器比 " " 更复杂{1}},必须在单独的 DCG 谓词/规则中定义逻辑,导致将单个解析器/生成器的逻辑拆分为两个定义(分离的一个和强制执行正确执行顺序的一个)。

这是解决此类问题的规范/标准方法还是我现在缺少的更通用、更优雅的解决方案?

附加信息:

我正在为 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),[]).
N = 0,Spaces = [] ;
N = 1,Spaces = [32] ;
N = 2,Spaces = [32,32] ;
N = 3,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

为了定位实例化错误的原因,我们将使用 。因此,我将加入一些 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),(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。最简单的方法是在 SWI library(clpz) 中使用 library(clpfd) 或其前身作为建议的 in another answer。是的,您可以手动执行此类操作,但是您正在复制已投入该库的工作。有时出于性能原因,这可能是有意义的,但它会以高昂的(漏洞百出的)代价来实现。

所以让我们看看@gusbro的解决方案,不要忘记添加

:- use_module(library(clpz)).  % SICStus or Scryer
:- use_module(library(clpfd)). % SWI
?- phrase(trivial_nat_space(N),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).

双引号

由于您想要优雅的代码,consider 用作文本的 single representation:字符列表。通过这种方式,您可以避免所有这些永远不会优雅的转换代码。在 Tau、Trealla 和 Scryer 等一些系统中,双引号项默认为 chars。在 SWI 中,像这样进行:

?- L = "ab",L = [_,_].
false.

?- phrase("ab","ab").
false.

?- set_prolog_flag(double_quotes,chars).
true.

?- L = "ab",_].
L = [a,b].

?- phrase("ab","ab").
true.

还有 library(double_quotes)

?- L = "ab",_].
L = "ab".

语法

最后,关于多向语法规则的一般情况有一些需要注意的地方。考虑一个谓词text_ast/2。对于一个抽象语法树,有无数有效的程序文本,它们都因诸如布局文本之类的琐碎工具而不同。因此,当仅给出 AST 时,这种一般关系不能终止。所以你需要一个额外的参数来指示文本是否应该是规范的。



1 事实上,在 DEC10 Prolog 中,算术表达式中变量的默认假设是值为零。为此,ISO Prolog 定义了实例化错误。


2 在 SICStus 的原生库 clpfd 中,同样的问题出现在 ?- N = 1+1,call(N #= 2). 上。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。