2016-06-16 22 views
8

Próbuję zaimplementować proste makro dla instrukcji switch w SWI-Prolog.Pisanie makr w SWI-Prologu

Jest to seria instrukcji warunkowych:

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

i jest to równoważne (ale znacznie wolniej) wyrażenie z tym samym skutkiem:

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

Użyłem wiele predykatów jak ten w aplikacji, ale spowalnia to znacznie. Czy możliwe jest zaimplementowanie tego switch predicate jako makra, aby zmienić go w normalne wyrażenie warunkowe podczas kompilacji, aby poprawić wydajność aplikacji?

+3

Najszybszy sposób najprawdopodobniej nie będzie nawet serią elementów if-then-elses, ale raczej zbiorem klauzul dla każdego przypadku. Dodatkowa korzyść: Będziesz mógł używać tych klauzul w większej liczbie kierunków. Sprawdź 'term_expansion/2' i' goal_expansion/2'. Aby przepisać terminy w czasie kompilacji. Zobacz "lista-map/2" SWI-Prolog w 'bibliotece (apply_macros)', aby zobaczyć, w jaki sposób można skompilować takie konstrukcje z wywołaniami predykatów pomocniczych. – mat

+0

Istnieje już sposób, aby dokładnie to, co opisujesz: predykat z wieloma klauzulami, gdzie pierwszym argumentem jest wyrażenie "switch" (tak jak @mat również wskazuje ....). Dlaczego odrzucasz tę idiomatyczną, szeroko stosowaną konstrukcję? –

+0

PS. Jeśli rzeczywiście chodzi o to, jak przeprowadzić ekspansję w czasie kompilacji, to w rzeczywistości jest to inne pytanie. –

Odpowiedz

3

minimalna próba: utwórz plik o nazwie 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). 

następnie używać go jak jak zwykle: na przykład w pliku 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) 
     ]). 

po kompilacji switch_test. pl:

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

true. 

edit powodu stwardnienia zamów uests, tutaj jest schemat kompilacja oddzielić punktach:

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

teraz skrypt testowy został owinięty w module, aby ułatwić dalszą listę:

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

a wynik po kompilacji 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). 

aby ułatwić debugowanie:

?- debug(switch). 

że wysyła wiadomość jak to podczas kompilacji:

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

uwaga: ten szkic oczywiście jest bardzo prawdopodobnie potrzeba więcej badań.

Jeśli zdecydujesz się na benchmarku ulepszenia (jeśli w ogóle), proszę nie używać stwierdzeń IO (jak writeln), gdyż te będą dominować w każdym razie te czasy realizacji.

+0

To nadal wygląda okropnie marnotrawnie. Dlaczego zamiast tego należy rozwinąć do poszczególnych klauzul? –

+0

@Boris: są problemy z kompilacją do poszczególnych klauzul, myślę, że ... na przykład, omijam kontekst lub wymyślam odpowiednią unikalną nazwę. Z pewnością możliwe do wykonania, ale nie w krótkiej odpowiedzi, która ma na celu przede wszystkim być na punkcie OP (bardzo precyzyjne) pytanie – CapelliC

+0

@CapelliC Czy możliwe jest automatyczne rozszerzenie 'switch ([a: b, c: d]). wiele klauzul takich jak 'case (a, b). Przypadek (c, d). "? –

1

Mam nadzieję, że korzystasz z powyższego writeln tylko do celów demonstracyjnych.Oto idiomatyczne sposób napisać ten sam program jak w pytaniu:

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

I to jest to, co ten program robi:

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

Ważne punkty:

  • Nie potrzeba writeln , najwyższy poziom to robi (jeśli naprawdę potrzebujesz pisać na wyjściu, możesz oczywiście to zrobić, ale nie zaszkodzi zachować go oddzielnie od reszty logiki).
  • Jest to zdecydowanie więcej miejsca i czasu efektywne, że którykolwiek z pozostałych propozycji
  • Można wyliczyć swoje przypadki, gdy wyrażenie przełącznik jest zmienną

Czy to możliwe, że nie w pełni zrozumieć this answer temu question of yours?.

Pamiętaj, że jeśli możesz zrobić wszystko w nagłówku predykatu, nie potrzebujesz nawet korpusu orzecznika: ponownie zobacz that same answer i mój przykład.

Wydaje się, że odrzucasz tę sugestię z powodu liczby argumentów, ale nie widzę, jak jakiekolwiek inne rozwiązanie rozwiąże ten problem. Czy potrafisz zademonstrować w swoim pytaniu, w jaki sposób chciałbyś zapisać swoje oświadczenie dotyczące przełącznika, dokładnie w sytuacji, gdy w grę wchodzi więcej argumentów?

Jeszcze jedno: jeśli masz wiele przypadków , łatwiejsze może być zapisanie ich na liście; następnie można użyć rozszerzenia terminów, aby dodać tabelę do bazy danych podczas kompilacji. Zobacz this question i przykład term_expansion pod koniec this answer; przykładem jest dosłowna kopia z SWI-Prolog documentation (spójrz na dół tej strony). Można oczywiście użyć goal_expansion zamiast term_expansion.