2016-06-16 34 views
8

Sto cercando di implementare una semplice macro per le istruzioni switch in SWI-Prolog.Scrivere macro in SWI-Prolog

Questa è una serie di istruzioni condizionali:

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

e questo è un equivalente (ma molto più lento) espressione con lo stesso effetto:

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

Ho usato molti predicati come questo in un'applicazione, ma questo rallenta notevolmente. È possibile implementare questo switch predicate come una macro in modo che venga modificata in una normale espressione condizionale in fase di compilazione, per migliorare le prestazioni dell'applicazione?

+3

Il modo più veloce probabilmente non sarà nemmeno una serie di if-then-elses, ma piuttosto un insieme di clausole per ogni caso. Ulteriore vantaggio: sarai in grado di utilizzare queste clausole in più direzioni. Controlla 'term_expansion/2' e' goal_expansion/2'. Per riscrivere i termini al momento della compilazione. Vedi 'maplist/2' di SWI-Prolog in' library (apply_macros) 'per vedere come si possono compilare tali costrutti per le chiamate di predicati ausiliari. – mat

+0

Esiste già un modo per fare esattamente ciò che descrivi: un predicato con più clausole, dove il primo argomento è l'espressione "interruttore" (come sottolinea anche @mat ....). Perché stai scartando questo costrutto idiomatico, ampiamente usato? –

+0

PS. Se la domanda riguarda effettivamente come eseguire l'espansione in fase di compilazione, allora si tratta di una domanda diversa. –

risposta

3

un tentativo minimo: creare un file denominato 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). 

poi usarlo come al solito: ad esempio, in un 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) 
     ]). 

dopo la compilazione di switch_test. pl:

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

true. 

modifica a causa di req multipla li ospiti, ecco uno schema di compilazione per separare clausole:

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

ora lo script di test è stato avvolto in un modulo, per facilitare ulteriormente lista:

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

e il risultato, dopo la compilazione 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). 

per facilitare il debug:

?- debug(switch). 

che emette un messaggio come questo durante la compilazione:

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

nota: questo schizzo, ovviamente, è molto probabilmente bisogno di ulteriori test.

Se si decide di eseguire il benchmark dei miglioramenti (se presenti), si prega di non utilizzare istruzioni IO (come writeln), dal momento che quelli avrebbero comunque dominato i tempi di esecuzione.

+0

Questo sembra ancora orribilmente sprecone. Perché non espandere invece le singole clausole? –

+0

@ Boris: ci sono problemi nella compilazione di singole clausole, penso ... per esempio, aggirare il contesto o sognare un nome univoco appropriato. Sicuramente fattibile, ma non in una risposta breve, che mira principalmente a puntare sulla domanda OP (molto precisa) – CapelliC

+0

@CapelliC Sarebbe possibile espandere automaticamente l'opzione '([a: b, c: d]).' più clausole come 'case (a, b). caso (c, d) .'? –

1

Spero che stiate usando lo writeln solo a scopo dimostrativo.Qui è il modo idiomatico di scrivere lo stesso programma nella tua domanda:

foo(a, case1). 
foo(b, case2). 
foo(c, case3). 

E questo è ciò che fa questo programma:

?- 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. 

punti importanti:

  • Non c'è bisogno di writeln , il livello principale lo fa (se hai davvero bisogno di scrivere in output, puoi ovviamente farlo, ma non sarebbe male tenerlo separato dal resto della logica).
  • Questo è sicuramente più spazio e tempo efficiente che uno qualsiasi degli altri suggerimenti
  • è possibile enumerare i vostri casi in cui l'espressione interruttore è una variabile

E 'possibile che non hai capito pienamente this answer a questo question of yours?.

Si noti che se si può fare tutto nella testa del predicato, non è nemmeno necessario il corpo del predicato: ancora, vedi that same answer e il mio esempio.

Sembra che tu abbia ignorato questo suggerimento a causa del numero di argomenti, ma non riesco a vedere come qualsiasi altra soluzione risolverà il problema. Puoi dimostrare nella tua domanda come vorresti scrivere la tua dichiarazione di switch, esattamente, quando sono coinvolti più argomenti?

Un'ultima cosa: se hai un sacco di casi, può essere più facile scriverle in una lista; quindi, puoi usare il termine espansione per aggiungere una tabella al tuo database in fase di compilazione. Vedere this question e l'esempio term_expansion verso la fine di this answer; l'esempio è una copia letterale dal SWI-Prolog documentation (guarda in fondo a quella pagina). È possibile utilizzare goal_expansion invece di term_expansion ovviamente.