问题描述
在 Prolog 中,传统上有两种表示字符序列的方式:
- 作为 chars 的列表,它们是长度为 1 的原子。
- 作为代码的列表,它们只是整数。整数将被解释为代码点,但未指定要应用的约定。作为一个(非常理智的)例子,在 SWI-Prolog 中,代码点的空间是 Unicode(因此,大致上,代码点整数的范围从 0 到 0x10FFFF)。
DCG 是一种编写从左到右列表处理代码的符号方式,旨在对“分解文本列表”进行解析。根据偏好,待处理的列表可以是字符列表或代码列表。但是,在写下常量时,字符/代码处理的表示法有所不同。通常以“字符样式”或“代码样式”编写 DCG 吗?或者甚至在模块导出 DCG 非终结符的情况下采用字符/代码样式以实现可移植性?
一些研究
以下符号可用于表示 DCG 中的常量
-
'a'
:一个 char(像往常一样:单引号表示一个原子,如果标记以小写字母开头,它们可以被省略。) -
0'a
:a
的代码。 -
['a','b']
:char 的列表。 -
[ 0'a,0'b ]
:代码列表,即a
和b
的代码(这样您就可以避免输入实际的代码点值)。 -
"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 中
旗帜
影响“标准代码”中 "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)
)。其中一个应用程序(像现在手机中常见的个人助理)使用了由语音识别组件产生的原子。
但让我们回到 chars 与 codes。遗留的实践和失败的标准化给 Prolog 留下了有问题的文本表示。 ASCII 给了我们一个单引号、一个反引号和一个双引号。对于原子使用单引号,可以选择使用例如反引号表示代码列表,双引号表示字符列表。或者反过来。相反,这就是标准化失败的地方,我们得到了有问题的 double_quotes
标志。不乏 Prolog 代码,它们对双引号术语的含义做出假设,因此根据 double_quotes
标志的隐含值起作用或中断(如果您认为这主要是遗留问题代码,再想一想)。猜猜当我们尝试组合需要不同标志值的代码时会发生什么?请注意,在几乎所有系统(包括支持模块的系统)中,标志值都是 global ......正如 Isabelle 在她的回答中所写的那样,明确设置标志是很好的一般建议。但正如我所解释的,并非没有问题。
某些系统为标志提供附加值。例如。 SWI-Prolog 还允许将该标志设置为 string
。 GNU Prolog 支持额外的 atom_no_escape
、chars_no_escape
和 codes_no_escape
。某些系统仅支持 codes
。某些系统还提供 back_quotes
标志。这个巴别塔意味着便携和弹性代码经常被迫使用原子来表示文本。但从性能的角度来看,这可能并不理想。
回到最初的问题。正如 Isabelle 所提到的,chars
通常是一个更具可读性(阅读、更易于调试)的选择。但是,根据 Prolog 系统,codes
可能会提供更好的性能。如果应用程序性能至关重要,则对两种解决方案进行基准测试。一些最近的 Prolog 系统(例如 Scryer-Prolog 或 Trealla Prolog)有效支持 chars
。较旧的系统可能会落后。
请注意,您的问题与 I/O 非常相关。在 ISO 之前,DEC-10 系列中的许多系统通过 get0/1
和 put/1
(以及 tty
的版本)支持单一类型的 I/O,同时提供字符和字节同时。那会出什么问题呢?今天,这是显而易见的。但是多八位字节字符集处理(如它所称的MOCSH)对于许多更加奇特的特性来说,就像在标准发布四分之一世纪之后的今天一样。毕竟,今天大多数人接受的 UTF-8 编码是 发明 1992-09 并于 1993 年首次发布。而且像 TRON 之类的许多项目一样,它也可能失败。其他一些编程语言因押注 UTF-16 编码而被烧毁。
标准所做的是将 I/O 拆分为字符和字节 I/O(以及它们对应的类型 text
和 binary
)。所以现在有 get_char/1
,get_byte/1
... _byte
版本都使用 0..255 范围内的整数是没有争议的(加上 -1 表示 EOF)。但是 _char
版本呢?解决此问题的唯一方法是同时提供 _char
和 _code
版本,从而提供双引号字符串和相关内置函数的 chars
和 codes
版本。标志 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。
如果您真的想编写同时支持 codes
和 chars
的程序,请使用类似 [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 之类的东西。