在 SWI-Prolog 中编写宏
Writing macros in SWI-Prolog
我正在尝试为 SWI-Prolog 中的 switch 语句实现一个简单的宏。
这是一系列条件语句:
(X = a ->
Output = case1;
X = b ->
Output = case2;
X = c ->
Output = case3).
这是具有相同效果的等效(但慢得多)表达式:
switch(X, [
a : (Output = case1),
b : (Output = case2),
c : (Output = case3)
])
我在一个应用程序中使用过许多像这样的谓词,但这大大降低了速度。是否可以将此 实现为宏,以便在编译时将其更改为普通条件表达式,以提高应用程序的性能?
最小尝试:创建一个名为 switch.pl
的文件
:- module(switch, []).
compile_caselist(X, [K:Clause], (X = K -> Clause)) :- !.
compile_caselist(X, [K:Clause|CaseList], ((X = K -> Clause);Translated)) :-
compile_caselist(X, CaseList, Translated).
:- multifile user:goal_expansion/2.
user:goal_expansion(F, G) :-
F = switch(X, CaseList),
compile_caselist(X, CaseList, G).
然后像往常一样使用它:例如,在文件中 switch_test.pl
:- use_module(switch).
test1(X) :-
X = a -> writeln(case1) ;
X = b -> writeln(case2) ;
X = c -> writeln(case3).
test2(X) :-
switch(X, [
a : writeln(case1),
b : writeln(case2),
c : writeln(case3)
]).
编译后switch_test.pl:
?- listing(test2).
test2(A) :-
( A=a
-> writeln(case1)
; A=b
-> writeln(case2)
; A=c
-> writeln(case3)
).
true.
编辑由于多次请求,这里是一个编译架构来分隔子句:
:- module(switch, []).
:- multifile user:term_expansion/2.
user:term_expansion((H:-B), [(H:-T)|SWs]) :-
collect_switches(H,B,T,SWs),
SWs \= [],
debug(switch, 'compiled <~w>~nto <~w>~nwith <~w>', [H,T,SWs]).
collect_switches(H,(A0;A),(B0;B),SWs) :-
collect_switches(H,A0,B0,S0),
collect_switches(H,A,B,S),
append(S0,S,SWs).
collect_switches(H,(A0,A),(B0,B),[S|SWs]) :-
call_switch(H,A0,B0,S), !,
collect_switches(H,A,B,SWs).
collect_switches(H,(A0,A),(A0,B),SWs) :-
collect_switches(H,A,B,SWs).
collect_switches(H,A,B,[S]) :-
call_switch(H,A,B,S), !.
collect_switches(_,C,C,[]).
call_switch(H,switch(X,CL),call(G,X),CTs) :-
functor(H,F,A),
R is random(1000000),
format(atom(G), '~s_~d_~d', [F,A,R]),
maplist({G}/[K:C,(H:-C)]>>(H=..[G,K]),CL,CTs).
现在测试脚本已经封装在一个模块中,以便于进一步列出:
:- module(switch_test, [test1/1,test2/1]).
:- use_module(switch).
test1(X) :-
X = a -> writeln(case1) ;
X = b -> writeln(case2) ;
X = c -> writeln(case3).
test2(X) :-
switch(X, [
a : writeln(case1),
b : writeln(case2),
c : writeln(case3)
]).
编译后的结果switch_test.pl
:
?- switch_test:listing.
test1(A) :-
( A=a
-> writeln(case1)
; A=b
-> writeln(case2)
; A=c
-> writeln(case3)
).
test2(A) :-
call(test2_1_362716, A).
test2_1_362716(a) :-
writeln(case1).
test2_1_362716(b) :-
writeln(case2).
test2_1_362716(c) :-
writeln(case3).
为了简化调试:
?- debug(switch).
编译时输出如下信息:
% [Thread pq] compiled <test2(_G121946)>
to <call(test2_1_362716,_G121946)>
with <[[(test2_1_362716(a):-writeln(case1)),(test2_1_362716(b):-writeln(case2)),(test2_1_362716(c):-writeln(case3))]]>
注意:此草图显然非常可能需要更多测试。
如果您决定对改进进行基准测试(如果有的话),请不要使用 IO 语句(如 writeln),因为它们无论如何都会支配执行时间。
我希望您使用上面的 writeln
仅用于演示目的。这是编写与您的问题相同的程序的惯用方法:
foo(a, case1).
foo(b, case2).
foo(c, case3).
这就是这个程序的作用:
?- foo(a, X).
X = case1.
?- foo(X, case1).
X = a.
?- foo(X, Y).
X = a,
Y = case1 ;
X = b,
Y = case2 ;
X = c,
Y = case3.
要点:
- 不需要
writeln
,顶层会做那个(如果你真的需要写输出,你当然可以这样做,但是把它和其他的分开也没什么坏处逻辑)。
- 这绝对比任何其他建议space更省时
- 当switch表达式是一个变量时,你可以枚举你的case
难不成你没有完全理解 to this ?
请注意,如果您可以在谓词的头部完成所有操作,您甚至不需要谓词主体:再次参见 和我的示例。
你似乎因为论据的数量而放弃了这个建议,但我看不出任何其他解决方案如何解决这个问题。当涉及更多参数时,您能否在您的问题中确切地说明您希望如何编写 switch 语句?
还有一点:如果你有很多案例,将它们写在列表中确实会更容易;然后,您可以使用术语扩展在编译时将 table 添加到您的数据库。请参阅 and the term_expansion
example towards the end of ; the example is a verbatim copy from the SWI-Prolog documentation(查看该页面的底部)。当然,您可以使用 goal_expansion
而不是 term_expansion
。
我正在尝试为 SWI-Prolog 中的 switch 语句实现一个简单的宏。
这是一系列条件语句:
(X = a ->
Output = case1;
X = b ->
Output = case2;
X = c ->
Output = case3).
这是具有相同效果的等效(但慢得多)表达式:
switch(X, [
a : (Output = case1),
b : (Output = case2),
c : (Output = case3)
])
我在一个应用程序中使用过许多像这样的谓词,但这大大降低了速度。是否可以将此
最小尝试:创建一个名为 switch.pl
的文件:- module(switch, []).
compile_caselist(X, [K:Clause], (X = K -> Clause)) :- !.
compile_caselist(X, [K:Clause|CaseList], ((X = K -> Clause);Translated)) :-
compile_caselist(X, CaseList, Translated).
:- multifile user:goal_expansion/2.
user:goal_expansion(F, G) :-
F = switch(X, CaseList),
compile_caselist(X, CaseList, G).
然后像往常一样使用它:例如,在文件中 switch_test.pl
:- use_module(switch).
test1(X) :-
X = a -> writeln(case1) ;
X = b -> writeln(case2) ;
X = c -> writeln(case3).
test2(X) :-
switch(X, [
a : writeln(case1),
b : writeln(case2),
c : writeln(case3)
]).
编译后switch_test.pl:
?- listing(test2).
test2(A) :-
( A=a
-> writeln(case1)
; A=b
-> writeln(case2)
; A=c
-> writeln(case3)
).
true.
编辑由于多次请求,这里是一个编译架构来分隔子句:
:- module(switch, []).
:- multifile user:term_expansion/2.
user:term_expansion((H:-B), [(H:-T)|SWs]) :-
collect_switches(H,B,T,SWs),
SWs \= [],
debug(switch, 'compiled <~w>~nto <~w>~nwith <~w>', [H,T,SWs]).
collect_switches(H,(A0;A),(B0;B),SWs) :-
collect_switches(H,A0,B0,S0),
collect_switches(H,A,B,S),
append(S0,S,SWs).
collect_switches(H,(A0,A),(B0,B),[S|SWs]) :-
call_switch(H,A0,B0,S), !,
collect_switches(H,A,B,SWs).
collect_switches(H,(A0,A),(A0,B),SWs) :-
collect_switches(H,A,B,SWs).
collect_switches(H,A,B,[S]) :-
call_switch(H,A,B,S), !.
collect_switches(_,C,C,[]).
call_switch(H,switch(X,CL),call(G,X),CTs) :-
functor(H,F,A),
R is random(1000000),
format(atom(G), '~s_~d_~d', [F,A,R]),
maplist({G}/[K:C,(H:-C)]>>(H=..[G,K]),CL,CTs).
现在测试脚本已经封装在一个模块中,以便于进一步列出:
:- module(switch_test, [test1/1,test2/1]).
:- use_module(switch).
test1(X) :-
X = a -> writeln(case1) ;
X = b -> writeln(case2) ;
X = c -> writeln(case3).
test2(X) :-
switch(X, [
a : writeln(case1),
b : writeln(case2),
c : writeln(case3)
]).
编译后的结果switch_test.pl
:
?- switch_test:listing.
test1(A) :-
( A=a
-> writeln(case1)
; A=b
-> writeln(case2)
; A=c
-> writeln(case3)
).
test2(A) :-
call(test2_1_362716, A).
test2_1_362716(a) :-
writeln(case1).
test2_1_362716(b) :-
writeln(case2).
test2_1_362716(c) :-
writeln(case3).
为了简化调试:
?- debug(switch).
编译时输出如下信息:
% [Thread pq] compiled <test2(_G121946)>
to <call(test2_1_362716,_G121946)>
with <[[(test2_1_362716(a):-writeln(case1)),(test2_1_362716(b):-writeln(case2)),(test2_1_362716(c):-writeln(case3))]]>
注意:此草图显然非常可能需要更多测试。
如果您决定对改进进行基准测试(如果有的话),请不要使用 IO 语句(如 writeln),因为它们无论如何都会支配执行时间。
我希望您使用上面的 writeln
仅用于演示目的。这是编写与您的问题相同的程序的惯用方法:
foo(a, case1).
foo(b, case2).
foo(c, case3).
这就是这个程序的作用:
?- foo(a, X).
X = case1.
?- foo(X, case1).
X = a.
?- foo(X, Y).
X = a,
Y = case1 ;
X = b,
Y = case2 ;
X = c,
Y = case3.
要点:
- 不需要
writeln
,顶层会做那个(如果你真的需要写输出,你当然可以这样做,但是把它和其他的分开也没什么坏处逻辑)。 - 这绝对比任何其他建议space更省时
- 当switch表达式是一个变量时,你可以枚举你的case
难不成你没有完全理解
请注意,如果您可以在谓词的头部完成所有操作,您甚至不需要谓词主体:再次参见
你似乎因为论据的数量而放弃了这个建议,但我看不出任何其他解决方案如何解决这个问题。当涉及更多参数时,您能否在您的问题中确切地说明您希望如何编写 switch 语句?
还有一点:如果你有很多案例,将它们写在列表中确实会更容易;然后,您可以使用术语扩展在编译时将 table 添加到您的数据库。请参阅 term_expansion
example towards the end of goal_expansion
而不是 term_expansion
。