2016-07-20 17 views
2

Ich bin derzeit nach dem Buch Lernen Sie einige Erlang für Great Good von Fred Herbert und einer der Abschnitte betrifft Macros.Wann sollten Makrofunktionen in Erlang verwendet werden?

Ich verstehe die Verwendung von Makros für Variablen (vor allem konstante Werte), aber ich verstehe nicht den Anwendungsfall für Makros als Funktionen. Zum Beispiel schreibt Herbert:

Definieren eines Makros "Funktion" ist ähnlich. Hier ist ein einfaches Makro verwendet, um eine Anzahl von anderen zu subtrahieren:

-define(sub(X, Y), X-Y). 

Warum nicht einfach diese definieren als eine Funktion an anderer Stelle? Warum ein Makro verwenden? Gibt es eine Art Leistungsvorteil vom Compiler oder ist dies nur ein "diese Funktion ist so einfach, lassen Sie uns nur in einer Zeile definieren" Art der Sache?

Ich versuche nicht, eine Debatte oder Präferenz Argument zu starten, aber nachdem ich einige Produktion Erlang-Code gesehen habe, habe ich angefangen, viele Makros Funktionsverwendung zu bemerken.

Antwort

2

In diesem Fall ist die eine offensichtliche Vorteil der Makro keine Funktion (-define(sub(X, Y), X-Y), die sicherer als -define(sub(X, Y), (X-Y)) wäre) ist, dass es sein kann, Wird als Wächter verwendet, da benutzerdefinierte Funktionsaufrufe verboten sind.

In vielen Fällen wäre es sonst sicherer, die Funktion als Inline zu definieren.

Auf der anderen Seite gibt es andere interessante Fälle, z. B. Assertionen in Tests oder Verknüpfungen, wo Sie wollen, dass einige lokale Kontext an der endgültigen Stelle zu halten.

Nehmen wir zum Beispiel an, ich möchte einen generischen Aufruf für einen Test machen, bei dem das Ziel "ein gegebenes Muster übereinstimmt und einen gegebenen Wert zurückgibt oder nach M Millisekunden fehlschlägt".

Ich kann dies nicht generisch mit Code machen, da Muster keine Datenstrukturen sind, die Sie herumtragen dürfen. Doch mit Makros:

-define(wait_for(PAT, Timeout), 
     receive 
      PAT -> VAL 
     after Timeout -> 
      error(timeout) 
     end). 

Dieses Makro kann dann verwendet werden, wie:

my_test() -> 
    Pid = start_whatever(), 
    %% ... 
    ?wait_for({'EXIT', Pid, Reason}, 5000), 
    ?assertMatch(shutdown, Reason). 

Dadurch dies, ich bin in der Lage, die Form von Text in einigen Tests zu vereinfachen, ohne ein Bündel von Verschachtelung zu benötigen und auf eine Weise, die mit Funktionen nicht möglich ist.

Sie beachten, dass die Behauptung selbst, wie durch eunit definiert eine Funktion Makro verwendet, und tut so etwas wie

-define(assertMatch(PAT, TERM), 
     %% funs to avoid leaking bindings into parent scope 
     (fun() -> 
      try 
       PAT = TERM, 
       true 
      catch _:_ -> 
       error({assertion_failed, ?LINE, ...}) 
      end 
     end)()). 

Diese Sie tragen Muster und Bindungen und tun Phantasie Formen ähnlich lässt, die nicht möglich sein könnte, Andernfalls.

In diesem letzten Fall werden Sie bemerken, dass ich das ?LINE Makro verwendet habe. Das ist ein weiterer Vorteil von Makros: Sie bewahren Informationen und Orte über die Aufruf-Site auf, z. B. den Modulnamen, die Zeilennummer usw. Dies ist nützlich, wenn solche Metadaten erforderlich sind, z. B. wenn Sie Testfehler melden.

1

Wenn Sie sich alten Code ansehen, könnten Makros verwendet werden, um kleine Funktionen einzubinden, unter der Annahme, dass Funktionsaufrufe sehr teuer sind. Ich bin mir nicht sicher, ob das jemals wahr war, aber darüber musst du dir heute keine Sorgen machen.

Makros können Konstanten verwendet werden, um zu definieren, wie

-define(MAX_TIMEOUT, 30 * 1000). 

%% ... 
    gen_server:call(my_server, {do_stuff, Data}, ?MAX_TIMEOUT), 
%% ... 

ich meist in Umgebungsvariablen für diesen Job übergeben lieber, aber es ist mehr Arbeit, um sie beim Start zu lesen und sie irgendwo verstauen und Accessoren schreiben.

Schließlich können Sie einige einfache metaprogramming tun:

-define(MAKE_REQUEST_FUN(Method), 
     Method(Request, HTTPOptions, Options) -> 
      httpc:request(Method, Request, HTTPOptions, Options)). 
?MAKE_REQUEST_FUN(get). 
?MAKE_REQUEST_FUN(put). 

%% Now we've defined a get/3 that can be called as 
%% get(Request, [], []). 
Verwandte Themen