编写可移植的标准Prolog程序及DCG支持代码解析
可移植标准Prolog程序编写要点
在编写可移植的标准Prolog程序时,兼容性文件的定义至关重要。一旦定义好兼容性文件,程序的其余部分就可以像在标准Prolog中一样使用相关谓词。
如果实现提供了某个谓词,但含义与标准不同,应尝试在兼容性文件中给出符合标准的定义。同时,要对程序和兼容性文件定义中的相关谓词进行重命名,以避免与实现提供的谓词冲突。虽然这样的程序可能不符合标准,但记录使其符合标准所需的转换并不困难。
在实践中,确定某个实现需要添加哪些标准Prolog谓词的过程,对于给定的实现或使用特定实现的特定站点,可能只需进行一次。
Prolog实现与标准的差异问题
Prolog实现有时会与标准存在差异,以下是一些需要注意的问题:
1.
字符处理
:标准Prolog中,字符是名称长度为1的原子。一些内置谓词处理字符,而另一些处理整数字符代码(如ASCII码),不同实现可能有所不同,且字符代码依赖于实现。
2.
字符串处理
:标准允许程序员为用双引号括起来的字符组成的Prolog术语选择一组固定的含义,但实现可能无法提供所有这些含义,因此在编写时应避免使用这种表示法。
3.
未知谓词处理
:标准允许程序员在调用未定义的谓词时选择一组固定的可能操作,但实现可能无法提供所有这些操作,因此不应依赖特定的行为。
4.
数据库更新
:当满足某个谓词的目标时,如果撤销该谓词的子句,可能会出现奇怪的情况。虽然标准规定了这种情况下应该发生的事情,但实现并不总是遵循,因此应避免编写会导致这种情况发生的程序。
5.
谓词名称
:内置谓词在不同实现中可能有不同的名称,例如
\+
可能被称为
not
。最好使用标准名称,必要时创建兼容性文件,根据非标准名称定义这些谓词。
6.
运算符优先级
:不同实现之间运算符优先级可能不同,最好依赖标准优先级,必要时(如果可能)在加载程序之前显式调用
op/3
来设置使用的运算符。
7.
项比较
:某些实现可能不提供项比较功能,项比较的结果可能依赖于字符代码,因此在一定程度上依赖于实现,尽管标准对项比较的操作有一些限制。
8.
输入/输出
:过去的Prolog系统在处理文件输入/输出方面差异很大,并不总是基于流,文件名的内部结构也依赖于实现。
9.
指令
:不同实现中可用于影响程序加载方式的指令不同。
10.
咨询程序
:标准未指定为此提供哪些谓词,因此不同实现会有所不同。
11.
算术运算
:Prolog标准指定了一组可用于算术表达式的函子,但实现可能在处理整数和浮点数、处理错误等方面有所不同。
12.
出现检查
:标准规定Prolog程序不应能够创建循环项,但一些实现甚至为循环项提供特殊支持,为了可移植性,不应使用它们。
部分标准谓词的定义
为了帮助创建自己的兼容性文件,下面给出了一些标准Prolog内置谓词的简单定义。由于这些定义限制在Clocksin和Mellish核心Prolog子集内,因此不能完全符合标准的所有方面。特别是,在核心子集中无法创建错误条件,因此有时这些定义在标准规定应出现错误条件时会失败。这些定义应支持谓词的大多数正确使用,但如果使用不当,可能会有不同的行为。
以下是标准谓词及其基于非标准谓词的定义总结:
| 标准谓词 | 基于非标准谓词的定义 |
| — | — |
|
atom_chars/2
|
name/2
|
|
number_chars/2
|
name/2
|
|
get_char/1
|
get0/1
,
name/2
|
|
put_char/1
|
put/1
,
name/2
|
|
dynamic/1
| (nothing) |
|
close/1
|
seeing/1
,
see/1
,
seen/0
,
telling/1
,
tell/1
,
told/0
|
|
current_input/1
|
seeing/1
|
|
current_output/1
|
telling/1
|
|
open/1
|
seeing/1
,
see/1
,
telling/1
,
tell/1
|
|
set_input/1
|
see/1
|
|
set_output/1
|
tell/1
|
|
write_canonical/1
|
display/1
|
|
\+/1
|
not/1
|
|
number/1
|
integer/1
|
|
@=</2
,
@>/2
,
@>=/2
,
@</2
|
name/2
|
字符处理相关代码
% atom_chars/2
%
% Translate between atoms and lists of characters
%
% Known problems:
%
% tends to fail rather than produce an error
%
% converts a sequence of characters which happen to be from '0' to '9'
%
% into a number, not an atom
atom_chars(Atom,Chars) :-
var(Atom), nonvar(Chars), !,
'$$collect_codes'(Chars,Codes),
name(Atom, Codes).
atom_chars(Atom,Chars) :-
name(Atom, Codes),
'$$cotlect_codes'(Chars,Codes).
'$$collect_codes'([Ch|Chs],[Co|Cos]) :-
(nonvar(Chs); nonvar(Cos)), !,
name(Ch,[Co]),
'$$collect_codes'(Chs,Cos).
'$$collect_codes'( [],[]).
% number_chars/2
%
% Translate between numbers and lists of characters
%
% Known problems:
%
% tends to fail rather than produce an error
%
% in fact also produces characters from non-numbers
number_chars(Num,Chars) :-
var(Num), nonvar(Chars), !,
'$$collect_codes'(Chars, Codes),
name(Num,Codes).
number_chars(Num,Chars) :-
nonvar(Num),
name(Num,Codes),
'$$collect_codes'(Chars,Codes).
% get_char/1
%
% Single character input
%
% Known problems:
%
% The definition of '$$end_of_file_code' needs to be updated so
%
% that it represents the character code used for "end of file".
get_char(Char) :- get0(Code), '$$code_to_char'(Code,Char).
'$$code_to_char'(Code,Char) :-
'$$end_of_file_code'(Code), !, Char=end_of_file.
'$$code_to_char'(Code,Char) :-
name(Char,[Code]),
'$$name_to_atom'(Char,Char1).
% some versions of 'name' create numbers in their first arguments
% make sure that the result really is an atom
'$$name_to_atom'(0,'0'):-!.
'$$name_to_atom'(1,'1'):-!.
'$$name_to_atom'(2,'2'):- !.
'$$name_to_atom'(3,'3'):-!.
'$$name_to_atom'(4,'4'):- !.
'$$name_to_atom'(5,'5'):-!.
'$$name_to_atom'(6,'6'):-!.
'$$name_to_atom'(7,'7'):-!.
'$$name_to_atom'(8,'8'):- !.
'$$name_to_atom'(9,'9'):- !.
'$$name_to_atom'(X,X).
'$$end_of_file_code'(-1).
% put_char/1
%
% Single character output
%
% Known problems:
%
% will fail if given a non-character atom (rather than produce an error)
put_char(X):- name(X,[C]), put(C).
指令相关代码
% dynamic/1
%
% Declare a set of
%
% Known problems:
%
% Does nothing. This at least means that dynamic directives will not
%
% create errors, but it does not address whatever might be required
%
% to allow a predicate to be dynamic in a given implementation.
?- op(1200,fx,':-').
?- op(1100,fx,dynamic).
dynamic(_).
流输入/输出相关代码
% The following predicates have to be used together
% NB the following predicates all make the assumption that the predicate
%
% '$$open'(Filename,Mode)
%
% (Filename an atom naming a file and Mode being 'read' or'write') is
% updated dynamically to reflect the files that are currently open for
% input and output.
%
% A stream is represented by a term '$$stream'(F) where F is the atom
% file name. The same structure is used for the streams user_input and
% user_output, with F = user_input or user_output.
%
% Known problems with this group of definitions:
%
% uses the file name inside the stream name - this convention cannot
%
% be relied on in Standard Prolog
%
% can only have one stream open at a time for a given file for input
%
% or output
%
% can't open a file called user_input or user_output
:- dynamic('$$open'/2).
% close/1
%
% Close a currently open stream
%
% Known problems:
%
% Fails if the stream is not open (rather than producing an error)
%
% Closes the file for both input and output (for whichever it is
%
% open for)
close(user_input):-!.
close(user_output):- !.
close('$$stream'(user_input)):- !.
close('$$stream'(user_output)):- !.
close('$$stream'(File)) :-
'$$open'(File,Mode), !,
'$$closefiles'(File).
'$$closefiles'(File) :-
retract('$$open'(File,Mode)),
'$$closefile'(File,Mode),
fail.
'$$closefiles'(_).
'$$closefile'(File,read) :- !,
seeing(Current),
see(File), seen,
see(Current).
'$$closefile'(File,write) :-
telling(Current),
tell(File), told,
tell(Current).
% current_input/1
%
% Test what the current input stream is
current_input('$$stream'(F)) :- seeing(F).
% current_output/1
%
% Test what the current output stream is
current_output('$$stream'(F)) :- telling(F).
% open/3
%
% Open a file for input or output
%
% Known problems:
open(File,read,'$$stream'(File)) :- !,
'$$close_if_open'(File,read), % close file if already open
seeing(Old),
% remember the current input
see(File),
% open again from the start
assert('$$open'(File,read)),
see(Old).
% but don't change the current input
open(File,write,'$$stream'( File)) :-
'$$close_if_open'(File,write), % close file if already open
telling(Old),
% remember the current output
tell(File),
% open again from the start
assert('$$open'(File,write)),
tell(Old).
% but don't change the current input
'$$close_if_open'(File,Mode) :-
retract('$$open'(File,Mode)), !,
'$$closefile'(File,Mode).
'$$close_if_open'(File,_).
% set_input/1
%
% Change current input
set_input(user_input):- !,
see(user).
set_input('$$stream'(user_input)):-!,
see(user).
set_input('$$stream'(F)) :-
'$$open'(F,read), !,
see(F).
% set_output/1
%
% Change current output
set_output(user_output):- !,
tell(user).
set_output('$$stream'(user_output)) :- !,
tell(user).
set_output('$$stream'(F)) :-
'$$open'(F,write), !,
tell(F).
其他杂项代码
% write_canonical/1
%
% Write a term, ignoring operator declarations
write_canonical(X) :- display(X).
% \+/1
%
% Negation as failure
?- op(900,fy,\+).
\+ X :- not(X).
% number/1
%
% Test for whether something is a number.
% Limitations: only succeeds for integers
number(X) :- integer(X).
% @<, @>, @=<, @>=
%
% Term comparison
%
% Limitations: only succeeds for atoms, assumes the Ascii character set
?- op(700,xfx,@<).
?- op(700,xfx,@>).
?- op(700,xfx,@=<).
?- op(700,xfx,@>=).
X @=< Y :- atom(X), atom(Y), X=Y, !.
X @=< Y :- X @< Y.
X @> Y :- Y @< X.
X @>= Y :- Y @=< X.
X @< Y :- atom(X), atom(Y), name(X,XC), name(Y,YC), '$$aless'(XC,YC).
'$$aless'([],[_|_]):- !.
'$$aless'([C|_],[C1 |_]):- C<C1, !.
'$$aless'([C|Cs],[C|Cs1]) :-
'$$aless'(Cs,Cs1).
DCG支持代码
Prolog标准并未规定Prolog实现应提供将DCG规则转换为Prolog的功能,但实际上许多实现都提供了该功能。以下Prolog程序对于希望使用DCG但实现不支持此功能的人可能会有用。除了谓词
phrase/2
、
phrase/3
和(可更新的)
C'/3
之外,它还提供了一个谓词
g/1
来咨询包含语法规则(可能还有普通Prolog)的文件。
g/1
谓词的工作方式是读取文件,并将其写入文件
dcg.tmp
,将任何语法规则转换为普通Prolog,然后正常咨询该文件。显然,用户不应将文件
dcg.tmp
用于其他用途。原始文件中的任何指令都将直接复制到
dcg.tmp
中,以便在咨询时遵守。由于在第一次读取文件时不遵守这些指令,因此在调用
g
之前必须进行读取文件所需的任何运算符声明。
% DCG code for Programming in Standard Prolog
%
% The following is part of Standard Prolog, but some Prolog
% systems may need it:
%
?- op(1200,xfx,-->).
%
% {...} is dealt with specially in the syntax of Standard Prolog,
% but in case it is not recognised, the following could be a way
% of having { and } be standard operators. However, any conjunctions
% and disjunctions inside {...} will have to be inside extra
% parentheses with spaces around the { and }, e.g. { (a(X), b(Y)) } .
%
?- op(901,fx,'{').
?- op(900,xf,'}').
% g(File)
%
% Consult a file File that may contain grammar rules. The
% predicate creates a new file dcg.tmp with the translated
% version of the original file and then consults that.
% The file can contain ordinary Prolog clauses as well, but
% any necessary operator declarations must be made before g is called
% (it does not obey any directives when the file is first read).
g(File) :-
open(File,read,In),
set_input(In),
open('dcg.tmp', write,Out),
set_output(Out),
repeat,
read(Term),
output_with_translation(Term),
Term = end_of_file,
!,
close(Out),
close(In),
consult('dcg.tmp').
% output_with_translation(Term)
%
% Outputs Term (in such a way that it can be read in as a clause)
% after translating it (if it happens to be a grammar rule)
output_with_translation(end_of_file):-!.
output_with_translation((X-->Y)) :- !,
translate((X-->Y),Z),
write_canonical(Z), write('.'), nl.
output_with_translation(X) :-
write_canonical(X), write('.'), nl.
% translate(+In,-Out)
%
% Translate a grammar rule
%
translate(((LHS_in1,LHS_in2) --> RHS_in), (LHS_out:- RHS_out)) :- !,
nonvar(LHS_in1),
islist(LHS_in2),
tag(LHS_in1,S0,Sn,LHS_out),
make_connects(LHS_in2,Sn,S1,Conn),
dcg_rhs(RHS_in,S0,S1,RHS_1),
dcg_and(Conn,RHS_1,RHS_2),
flatten2(RHS_2,RHS_out).
translate((LHS_in --> RHS_in), (LHS_out:- RHS_out)) :-
nonvar(LHS_in),
tag(LHS_in,S0,Sn,LHS_out),
dcg_rhs(RHS_in,S0,Sn,RHS_1),
flatten2(RHS_1,RHS_out).
% dcg_rhs(+RHS,SO,SI,-Translation)
%
% Translate the RHS of a grammar rule into a
% conjunction of Prolog goals. SO and S1 are
% variables to be used for the input and output
% list arguments of the whole conjunction (these
% are the variables used for the input and output
% list arguments for the head of the clause)
dcg_rhs(X, S0, S, phrase(X,S0,S)) :- var(X), !.
dcg_rhs((RHS_in1,RHS_in2),S0,Sn,RHS_out):- !,
dcg_rhs(RHS_in1,S0,S1,RHS_out1),
dcg_rhs(RHS_in2,S1,Sn,RHS_out2),
dcg_and(RHS_out1,RHS_out2,RHS_out).
dcg_rhs((RHS_in1;RHS_in2),S0,Sn,(RHS_out1;RHS_out2)):- !,
dcg_or(RHS_in1,S0,Sn,RHS_out1),
dcg_or(RHS_in2,S0,Sn,RHS_out2).
dcg_rhs({RHS_in},S0,S0,RHS_in) :- !.
dcg_rhs(!,S0,S0,!) :- !.
dcg_rhs(RHS_in,S0,Sn,C) :-
% terminal(s)
islist(RHS_in), !,
make_connects(RHS_in,S0,Sn,C).
dcg_rhs(RHS_in,S0,Sn,RHS_out):-
% single non-terminal
tag(RHS_in,S0,Sn,RHS_out).
%
% Auxiliary predicates
%
% dcg_or(+RHS,SO,SI,-Translation)
%
% As dcg_rhs, except for goals that will be part of a
% disjunction. dcg_rhs can instantiate the first list
% argument (SO) (by making it the same as the second
% or a list with some terminals in it), but that can't
% be done here (it will mess
% up the other disjuncts). Instead, if that happens, an
% explicit = goal is included in the output.
dcg_or(In,S0,Sn,Out) :-
dcg_rhs(In,S1,Sn,Out1), % using new first list argument S1
( var(S1),
\+ S1 == Sn, !,
% if S1 has not been set
S0=S1,
%
Out=Out1;
% return what was computed (S1)
Out=(S0=S1,Out1)). % otherwise link S0,S1 with an = goal
% Create a conjunction, flattening if possible
dcg_and(true,In,In) :- !.
dcg_and(In,true,In) :- !.
dcg_and(In1,In2,(In1,In2)).
% tag(+In,S0,Sl,-Out)
%
% In is a term representing a DCG non-terminal. Out
% is the result of adding S0 and S1 as extra arguments
tag(In,S0,Sn,Out) :-
In=..[Predicate|Arguments],
dcg_append(Arguments,[S0,Sn],New_arguments),
Out=..[Predicate|New_arguments].
% flatten 2 (+Seq,-FSeq)
%
% Given a sequence of terms connected by','/2
% (possibly with embedded sequences), produces a
% "flattened" form
flatten2(In,In) :- var(In), !.
flatten2((In1,In2),Out1) :- !,
flatten1(In1,Out1,Out2),
flatten2(In2,Out2).
flatten2(In,In).
flatten1(In1,(In1,In2),In2) :-
var(In1), !.
flatten1((In1,In2),Out1,In3):- !,
flatten1(In1,Out1,Out2),
flatten1(In2,Out2,In3).
flatten1(In1,(In1,In2),In2).
islist([]).
islist([_|_]).
dcg_append([],X,X).
dcg_append([X|L],L1,[X|L2]):- dcg_append(L,L1,L2).
% make_connects(+Terminals,SO,Sl,-Goals)
%
% Create the X' goals for a list of terminals. SO and SI
% are to be instantiated to the input and output list
% arguments.
make_connects([First|Rest],S0,Sn,Conns) :-
nonvar(Rest), !,
make_connects(Rest,S1,Sn,Cs),
dcg_and('C'(S0,First,S1),Cs,Conns).
make_connects([],S,S,true).
% Predicates that can be called/redefined by the user
phrase(T, S):- phrase(T, S, []).
phrase(T, S0, S ) :- tag(T, S0, S, G), call(G).
'C'([W|Ws],W,Ws).
通过以上的代码和分析,我们可以更好地编写可移植的标准Prolog程序,并在实现不支持DCG规则转换时使用提供的代码来支持DCG。在实际应用中,要充分考虑不同实现之间的差异,确保程序的正确性和可移植性。
编写可移植的标准Prolog程序及DCG支持代码解析(续)
DCG支持代码详细解析
代码整体流程
下面通过一个mermaid流程图来展示
g/1
谓词处理包含语法规则文件的整体流程:
graph TD;
A[开始] --> B[打开输入文件];
B --> C[设置输入流为输入文件];
C --> D[打开临时文件dcg.tmp];
D --> E[设置输出流为临时文件];
E --> F[循环读取输入文件内容];
F --> G{是否为语法规则};
G -- 是 --> H[翻译语法规则];
G -- 否 --> I[直接写入临时文件];
H --> J[将翻译后的规则写入临时文件];
F --> K{是否到达文件末尾};
K -- 否 --> F;
K -- 是 --> L[关闭临时文件];
L --> M[关闭输入文件];
M --> N[咨询临时文件];
N --> O[结束];
从这个流程图可以清晰地看到,
g/1
谓词首先打开输入文件和临时文件,然后逐行读取输入文件内容。如果遇到语法规则则进行翻译后写入临时文件,普通内容则直接写入。当读取完整个文件后,关闭文件并咨询临时文件。
各关键谓词详细分析
-
output_with_translation(Term)-
该谓词用于输出翻译后的内容。如果输入的
Term是语法规则(X --> Y形式),则调用translate进行翻译,然后将翻译结果以规范形式写入临时文件并换行。如果不是语法规则,则直接以规范形式写入并换行。 - 代码示例:
-
该谓词用于输出翻译后的内容。如果输入的
output_with_translation(end_of_file):-!.
output_with_translation((X-->Y)) :- !,
translate((X-->Y),Z),
write_canonical(Z), write('.'), nl.
output_with_translation(X) :-
write_canonical(X), write('.'), nl.
-
translate(+In,-Out)- 此谓词用于翻译语法规则。对于不同形式的语法规则有不同的处理方式。当规则的左部有两个部分时,会进行一系列的处理,包括标记左部、创建连接、翻译右部等操作,最终将结果扁平化。对于普通的语法规则,也会进行类似的处理。
- 代码示例:
translate(((LHS_in1,LHS_in2) --> RHS_in), (LHS_out:- RHS_out)) :- !,
nonvar(LHS_in1),
islist(LHS_in2),
tag(LHS_in1,S0,Sn,LHS_out),
make_connects(LHS_in2,Sn,S1,Conn),
dcg_rhs(RHS_in,S0,S1,RHS_1),
dcg_and(Conn,RHS_1,RHS_2),
flatten2(RHS_2,RHS_out).
translate((LHS_in --> RHS_in), (LHS_out:- RHS_out)) :-
nonvar(LHS_in),
tag(LHS_in,S0,Sn,LHS_out),
dcg_rhs(RHS_in,S0,Sn,RHS_1),
flatten2(RHS_1,RHS_out).
-
dcg_rhs(+RHS,SO,SI,-Translation)- 该谓词用于将语法规则的右部翻译为Prolog目标的合取。根据右部的不同形式(变量、合取、析取、花括号内内容、感叹号、列表、单个非终结符等)进行不同的处理。
- 代码示例:
dcg_rhs(X, S0, S, phrase(X,S0,S)) :- var(X), !.
dcg_rhs((RHS_in1,RHS_in2),S0,Sn,RHS_out):- !,
dcg_rhs(RHS_in1,S0,S1,RHS_out1),
dcg_rhs(RHS_in2,S1,Sn,RHS_out2),
dcg_and(RHS_out1,RHS_out2,RHS_out).
dcg_rhs((RHS_in1;RHS_in2),S0,Sn,(RHS_out1;RHS_out2)):- !,
dcg_or(RHS_in1,S0,Sn,RHS_out1),
dcg_or(RHS_in2,S0,Sn,RHS_out2).
dcg_rhs({RHS_in},S0,S0,RHS_in) :- !.
dcg_rhs(!,S0,S0,!) :- !.
dcg_rhs(RHS_in,S0,Sn,C) :-
% terminal(s)
islist(RHS_in), !,
make_connects(RHS_in,S0,Sn,C).
dcg_rhs(RHS_in,S0,Sn,RHS_out):-
% single non-terminal
tag(RHS_in,S0,Sn,RHS_out).
-
dcg_or(+RHS,SO,SI,-Translation)-
该谓词处理析取情况。它使用新的第一个列表参数
S1调用dcg_rhs进行翻译。如果S1未被设置且不等于Sn,则将S0与S1进行绑定并返回计算结果;否则,在输出中添加一个S0 = S1的目标。 - 代码示例:
-
该谓词处理析取情况。它使用新的第一个列表参数
dcg_or(In,S0,Sn,Out) :-
dcg_rhs(In,S1,Sn,Out1), % using new first list argument S1
( var(S1),
\+ S1 == Sn, !,
% if S1 has not been set
S0=S1,
%
Out=Out1;
% return what was computed (S1)
Out=(S0=S1,Out1)). % otherwise link S0,S1 with an = goal
-
dcg_and(In1,In2,Result)-
用于创建合取。如果其中一个参数为
true,则直接返回另一个参数;否则,将两个参数组合成(In1, In2)形式。 - 代码示例:
-
用于创建合取。如果其中一个参数为
dcg_and(true,In,In) :- !.
dcg_and(In,true,In) :- !.
dcg_and(In1,In2,(In1,In2)).
-
tag(+In,S0,Sl,-Out)-
该谓词用于为DCG非终结符添加
S0和S1作为额外参数。通过=..操作将原项拆分为谓词和参数列表,然后将S0和S1添加到参数列表末尾,最后重新组合成新的项。 - 代码示例:
-
该谓词用于为DCG非终结符添加
tag(In,S0,Sn,Out) :-
In=..[Predicate|Arguments],
dcg_append(Arguments,[S0,Sn],New_arguments),
Out=..[Predicate|New_arguments].
-
flatten2(+Seq,-FSeq)- 用于将由逗号连接的项序列扁平化。如果输入是变量则直接返回;如果是嵌套的序列,则递归地进行扁平化处理。
- 代码示例:
flatten2(In,In) :- var(In), !.
flatten2((In1,In2),Out1) :- !,
flatten1(In1,Out1,Out2),
flatten2(In2,Out2).
flatten2(In,In).
-
make_connects(+Terminals,SO,Sl,-Goals)- 为终端列表创建目标。如果终端列表不为空,则递归地处理剩余部分,并将当前终端与输入输出列表建立连接。
- 代码示例:
make_connects([First|Rest],S0,Sn,Conns) :-
nonvar(Rest), !,
make_connects(Rest,S1,Sn,Cs),
dcg_and('C'(S0,First,S1),Cs,Conns).
make_connects([],S,S,true).
编写可移植Prolog程序的最佳实践
在编写可移植的标准Prolog程序时,我们可以遵循以下最佳实践:
1.
兼容性文件的使用
- 当实现提供的谓词含义与标准不同时,创建兼容性文件。在兼容性文件中给出符合标准的定义,并对相关谓词进行重命名,避免冲突。例如,若
\+
在某些实现中被称为
not
,可以在兼容性文件中定义
\+ X :- not(X)
。
2.
遵循标准规范
- 尽量使用标准名称的谓词和运算符,依赖标准优先级。如使用标准的
atom_chars/2
而不是非标准的类似谓词,对于运算符优先级,必要时使用
op/3
进行显式设置。
3.
避免依赖特定实现特性
- 不要依赖特定实现的特殊功能,如某些实现对循环项的特殊支持。为了保证可移植性,应遵循标准规定,不使用会导致创建循环项的代码。
4.
处理差异情况
- 对于不同实现可能存在差异的方面,如字符处理、输入输出等,要进行充分的测试和处理。例如,在处理字符时,要考虑不同实现对字符代码和字符原子的处理方式。
总结
编写可移植的标准Prolog程序需要充分考虑不同实现之间的差异。通过创建兼容性文件、遵循标准规范、避免依赖特定实现特性等方法,可以提高程序的可移植性。同时,对于DCG规则的支持,当实现不提供转换功能时,可以使用上述提供的代码进行支持。在实际应用中,要根据具体情况灵活运用这些知识和代码,确保程序在不同的Prolog实现中都能正确运行。
通过本文的详细介绍,我们对编写可移植的标准Prolog程序以及DCG支持代码有了更深入的理解。希望这些内容能帮助开发者在Prolog编程中更加得心应手,编写出高质量、可移植的程序。
超级会员免费看
695

被折叠的 条评论
为什么被折叠?



