应该编写文本处理 DCG 来处理代码还是字符?还是两者都有?

问题描述

在 Prolog 中,传统上有两种表示字符序列的方式:

  • 作为 chars 的列表,它们是长度为 1 的原子。
  • 作为代码的列表,它们只是整数。整数将被解释为代码点,但未指定要应用的约定。作为一个(非常理智的)例子,在 SWI-Prolog 中,代码点的空间是 Unicode(因此,大致上,代码点整数的范围从 0 到 0x10FFFF)。

DCG 是一种编写从左到右列表处理代码的符号方式,旨在对“分解文本列表”进行解析。根据偏好,待处理的列表可以是字符列表或代码列表。但是,在写下常量时,字符/代码处理的表示法有所不同。通常以“字符样式”或“代码样式”编写 DCG 吗?或者甚至在模块导出 DCG 非终结符的情况下采用字符/代码样式以实现可移植性?

一些研究

以下符号可用于表示 DCG 中的常量

  • 'a'一个 char(像往常一样:单引号表示一个原子,如果标记以小写字母开头,它们可以被省略。)
  • 0'aa代码
  • ['a','b']char 的列表。
  • [ 0'a,0'b ]代码列表,即ab代码(这样您就可以避免输入实际的代码点值)。
  • "a" 代码列表。传统上,双引号字符串被分解成一个代码列表,这种表示法也适用于 DCG 上下文中的 SWI-Prolog,即使 SWI-Prolog 将“双引号字符串”映射到特殊的string 其他数据类型。
  • `0123`。传统上,反引号内的文本被映射到一个原子(我认为,95 ISO 标准只是避免对反引号字符串的含义进行具体说明。“这将是 ISO/ IEC 13211 将反引号字符串定义为表示字符串常量。")。在 SWI-Prolog 中,反引号内的文本被分解为代码列表,除非已设置标志 back_quotes 以要求不同的行为。

示例

字符样式

尝试识别“字符样式”中的“任何数字”并使其在 C 中可用:

zero(C) --> [C],{C = '0'}. 

nonzero(C) --> [C],{member(C,['1','2','3','4','5','6','7','8','9'])}.

any_digit(C) --> zero(C).
any_digit(C) --> nonzero(C).

代码风格

尝试识别“代码样式”中的“任何数字”:

zero(C) --> [C],{C = 0'0}.

nonzero(C) --> [C],[0'1,0'2,0'3,0'4,0'5,0'6,0'7,0'8,0'9])}.

any_digit(C) --> zero(C).
any_digit(C) --> nonzero(C).

字符/代码透明样式

DCG 可以通过复制涉及常量的规则来编写为“字符/代码透明样式”。在上面的例子中:

zero(C) --> [C],{C = '0'}. 
zero(C) --> [C],'9'])}.
nonzero(C) --> [C],0'9])}.

any_digit(C) --> zero(C).
any_digit(C) --> nonzero(C).

上面也接受一系列交替的代码和字符(因为不能输入内容列表)。这可能不是问题)。 生成时,会得到任意不需要的字符/代码混合,然后需要添加剪切。

Char/Code 透明样式采用额外的 Mode 指示符

另一种方法是明确指示模式。看起来很干净:

zero(C,chars) --> [C],{C = '0'}. 
zero(C,codes) --> [C],{C = 0'0}.

nonzero(C,'9'])}.
nonzero(C,0'9])}.

any_digit(C,Mode) --> zero(C,Mode).
any_digit(C,Mode) --> nonzero(C,Mode).

使用方言特征的字符/代码透明样式

或者,可以使用 Prolog 方言的特性来实现字符/代码透明度。在 SWI-Prolog 中,有 code_type/2,它实际上适用于代码和字符(有一个相应的 char_type/2,但恕我直言,无论如何应该只有 chary_type/2 适用于字符和代码)和“数字类”代码和字符产生复合digit(X)

?- code_type(0'9,digit(X)).
X = 9.

?- code_type('9',digit(X)).
X = 9.

?- findall(W,code_type('9',W),B).
B = [alnum,csym,prolog_identifier_continue,ascii,digit,graph,to_lower(57),to_upper(57),digit(9),xdigit(9)].

因此可以编写此代码以获得清晰的字符/代码透明度:

zero(C) --> [C],{code_type(C,digit(0)}. 

nonzero(C) --> [C],digit(X),X>0}.

any_digit(C) --> zero(C).
any_digit(C) --> nonzero(C).

特别是在 SWI-Prolog 中

SWI-Prolog 认更喜欢代码。试试这个:

旗帜

影响“标准代码”中 "string"`string` 的解释。认情况下,"string" 被解释为原子“字符串”,而 `string` 被解释为“代码列表”。

在 DCG 之外,以下在 SWI-Prolog 中保持不变,所有标志都为认值:

?- string("foo"),\+atom("foo"),\+is_list("foo").
true.

?- L=`foo`.
L = [102,111,111].

但是,在 DCG 中,"string"`string` 认都被解释为“代码”。

不改变任何设置,考虑这个 DCG:

representation(double_quotes)    --> "bar".            % SWI-Prolog decomposes this into CODES 
representation(back_quotes)      --> `bar`.            % SWI-Prolog decomposes this into CODES
representation(explicit_codes_1) --> [98,97,114].      % explicit CODES (as obtained via atom_codes(bar,Codes))
representation(explicit_codes_2) --> [0'b,0'a,0'r].    % explicit CODES 
representation(explicit_chars)   --> ['b','a','r'].    % explicit CHARS

以上哪个匹配代码

?- 
findall(X,(atom_codes(bar,Codes),phrase(representation(X),Codes,[])),Reps).

Reps = [double_quotes,back_quotes,explicit_codes_1,explicit_codes_2].

以上哪个匹配字符?

?- findall(X,(atom_chars(bar,Chars),Chars,Reps).
Reps = [explicit_chars].

当以 swipl --Traditional 开始 swipl 时,反引号表示被 Syntax error: Operator expected 拒绝,但其他方面没有任何变化。

解决方法

Prolog 标准 (6.3.7) 说:

双引号列表要么是原子(6.3.1.3)要么是列表(6.3.5)。

因此,以下应该成功:

Welcome to SWI-Prolog (threaded,64 bits,version 7.6.4)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background,visit http://www.swi-prolog.org
For built-in help,use ?- help(Topic). or ?- apropos(Word).

?- Foo = "foo",(atom(Foo) ; Foo = [F,O,O]).
false.

所以 SWI-Prolog 默认不是 Prolog。没关系,但是如果您想了解 SWI-Prolog 的非 Prolog 行为,请调整问题上的标签。

根据定义,双引号列表在默认情况下完全没有用,即使在符合 Prolog 的情况下:它们可能表示原子,因此无论字符/代码的区别如何,您甚至都不知道双引号列表实际上是一个列表。如果“列表”实际上是一个原子,即使 DCG 只关心“文本”的结构属性(例如,它是否是回文)也是无用的。

因此,想要处理带有 DCG 的文本的 Prolog 程序必须在启动时将 double_quotes 标志强制为它想要的值。您可以在代码和字符之间进行选择。代码与字符相比没有任何优势,但它们在可读性和可键入性方面确实存在劣势。因此:

答案:使用字符。明确设置 double_quotes 标志。

,

我应该首先注意到“应该编写文本处理 DCG 来处理代码还是字符?或两者?” 问题的答案可以两者都不是 . DCG 通过对线程状态使用隐式差异列表来工作。但是该差异列表的元素可以不是字符或代码。这取决于文本标记化的输出以及文本处理究竟需要什么。例如。我曾研究并遇到过 Prolog NLP 应用程序,其中代码/字符仅用于基本标记化,并且使用原子或具体化标记数据的复合术语(例如 v(Verb)n(Noun))。其中一个应用程序(像现在手机中常见的个人助理)使用了由语音识别组件产生的原子。

但让我们回到 charscodes。遗留的实践和失败的标准化给 Prolog 留下了有问题的文本表示。 ASCII 给了我们一个单引号、一个反引号和一个双引号。对于原子使用单引号,可以选择使用例如反引号表示代码列表,双引号表示字符列表。或者反过来。相反,这就是标准化失败的地方,我们得到了有问题的 double_quotes 标志。不乏 Prolog 代码,它们对双引号术语的含义做出假设,因此根据 double_quotes 标志的隐含值起作用或中断(如果您认为这主要是遗留问题代码,再想一想)。猜猜当我们尝试组合需要不同标志值的代码时会发生什么?请注意,在几乎所有系统(包括支持模块的系统)中,标志值都是 global ......正如 Isabelle 在她的回答中所写的那样,明确设置标志是很好的一般建议。但正如我所解释的,并非没有问题。

某些系统为标志提供附加值。例如。 SWI-Prolog 还允许将该标志设置为 string。 GNU Prolog 支持额外的 atom_no_escapechars_no_escapecodes_no_escape。某些系统仅支持 codes。某些系统还提供 back_quotes 标志。这个巴别塔意味着便携弹性代码经常被迫使用原子来表示文本。但从性能的角度来看,这可能并不理想。

回到最初的问题。正如 Isabelle 所提到的,chars 通常是一个更具可读性(阅读、更易于调试)的选择。但是,根据 Prolog 系统,codes 可能会提供更好的性能。如果应用程序性能至关重要,则对两种解决方案进行基准测试。一些最近的 Prolog 系统(例如 Scryer-Prolog 或 Trealla Prolog)有效支持 chars。较旧的系统可能会落后。

,

请注意,您的问题与 I/O 非常相关。在 ISO 之前,DEC-10 系列中的许多系统通过 get0/1put/1(以及 tty 的版本)支持单一类型的 I/O,同时提供字符和字节同时。那会出什么问题呢?今天,这是显而易见的。但是多八位字节字符集处理(如它所称的MOCSH)对于许多更加奇特的特性来说,就像在标准发布四分之一世纪之后的今天一样。毕竟,今天大多数人接受的 UTF-8 编码是 发明 1992-09 并于 1993 年首次发布。而且像 TRON 之类的许多项目一样,它也可能失败。其他一些编程语言因押注 UTF-16 编码而被烧毁。

标准所做的是将 I/O 拆分为字符和字节 I/O(以及它们对应的类型 textbinary)。所以现在有 get_char/1,get_byte/1 ... _byte 版本都使用 0..255 范围内的整数是没有争议的(加上 -1 表示 EOF)。但是 _char 版本呢?解决此问题的唯一方法是同时提供 _char_code 版本,从而提供双引号字符串和相关内置函数的 charscodes 版本。标志 double_quotes 的默认值是实现定义的 (7.11.2.5)。

通过这种方式,具有大量 DEC-10 遗产的系统可以继续明确使用代码。对他们来说,整数意味着整数或字节或字符。但是这种系统的用户仍然可以使用更好的编码。无需处理 1977 年以来的此类遗留问题的新系统选择默认为 chars,如 Tau、Scryer 和 Trealla。就传统而言,请注意 Prolog I,通常称为 Marseille Prolog,确实将双引号字符串编码为长度为 1 的原子列表。在 1972 年 Prolog 的初步版本中,通常称为 Prolog 0,字符串被编码为 nil-s-t-r-i-n-g qua boum 以促进词干提取。无论如何,根本不存在单个字符代码。

chars 的优势应该是显而易见的。阅读和调试要容易得多,特别是如果您有部分实例化的字符串,比如 [a,X,c][97,99],这在使用 library(diadem) 泛化查询时经常发生。写起来也短了一点。并且,可以使用双引号字符串 for printing answers

如果您真的想编写同时支持 codeschars 的程序,请使用类似 [Ch] = "a" where {{1} } 现在是原子 Ch 或整数 97 或 129 或您使用的任何处理器字符集。这一切都取决于 Prolog 标志 a。更简洁的你可以写

double_quotes

更重要的是 nonzero(C) --> [C],{member(C,"123456789")}. 仍然成立。

但是,在同一应用程序中更改该标志肯定不是一个好主意(也不要切换到值 phrase("abc","abc") 或某些不符合标准的值)。

((当使用 atom 时,请注意 chars 中的单引号有点误导,因为单引号没有任何用途。相反,如果您想确保即使存在 C = 'a' 的运算符声明,代码也是有效的。当 a 作为函子的参数或列表的元素出现时,不需要圆括号,但它们经常在运算符声明中冗余使用.))

,

您做出了错误的假设。这些不是“字符”:

foo_or_bar(foo) --> "foo".

"foo" 是一个字符串,在 SWI-Prolog 中,但这在 DCG 规则定义中完美地工作。阅读此内容的地方是 here,特别是:

DCG 文字

尽管表示为代码列表是 DCG 中处理的正确表示,但 DCG 翻译器可以识别文字并将其转换为正确的表示。这样的代码不需要修改。

您的所有其他建议都是不必要的,您应该明确枚举所有可能的“非零”、数字等,或者使用该库。

PS:如果您的主要目标是编写在任何 Prolog 上运行的代码,您不妨改用 Logtalk 之类的东西。