Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing macros in SWI-Prolog

I am trying to implement a simple macro for switch-statements in SWI-Prolog.

This is a series of conditional statements:

(X = a ->
    Output = case1;
X = b ->
    Output = case2;
X = c ->
    Output = case3).

and this is an equivalent (but much slower) expression with the same effect:

switch(X, [
    a : (Output = case1),
    b : (Output = case2),
    c : (Output = case3)
])

I have used many predicates like this one in an application, but this slows it down considerably. Is it possible to implement this switch predicate as a macro so that it will be changed into a normal conditional expression at compile-time, to improve the application's performance?

like image 942
Anderson Green Avatar asked Jun 16 '16 18:06

Anderson Green


1 Answers

a minimal attempt: create a file named 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).

then use it like as usual: for instance, in a file 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)
       ]).

after compilation of switch_test.pl:

?- listing(test2).
test2(A) :-
    (   A=a
    ->  writeln(case1)
    ;   A=b
    ->  writeln(case2)
    ;   A=c
    ->  writeln(case3)
    ).

true.

edit due to multiple requests, here is a compilation schema to separate clauses:

:- 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).

now the test script has been wrapped in a module, to ease further listing:

:- 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)
       ]).

and the result, after compiling 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).

to ease debugging:

?- debug(switch).

that outputs a message like this when compiling:

% [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))]]>

note: this sketch obviously is very likely to need more testing.

If you decide to benchmark the improvements (if any), please don't use IO statements (like writeln), since those would dominate anyway the execution timings.

like image 56
CapelliC Avatar answered Oct 10 '22 20:10

CapelliC