2016-04-05 6 views
4

Ist es möglich, jedes geladene Modul zu finden, das ein bestimmtes Verhalten angenommen hat?Finden Sie alle Module, die das Verhalten übernommen haben

Ich baue eine sehr einfache Chat-Bot und ich möchte einige coole Befehle machen, aber um das zu erreichen, brauche ich eine Möglichkeit, mehrere Befehle zu implementieren, vorzugsweise ohne sie hart zu codieren.

Jeder Befehl wäre eine Funktion, die drei Parameter (message, author, chat_channel_ref) benötigt und true oder false zurückgibt, egal ob sie übereinstimmt oder nicht.

Wenn ich durch das Elixir-Tutorial stöbere, habe ich Behaviors gefunden, das meinen Bedürfnissen gut entsprechen kann, wenn ich alle Module finden könnte, die sie adoptiert haben. Hat jemand von euch das schon mal gemacht? Was könnte ich noch tun? Ich dachte auch über "Verwendung" nach (im Einsatz würde ich Code ausführen, um das aktuelle Modul zu einer vom Agenten gehaltenen Liste hinzuzufügen).

+1

Wie viele davon werden Sie haben? Vielleicht ist es einfacher, eine Liste von ihnen irgendwo in Ihrem Quellcode zu führen, vielleicht in einer Supervisor-Spezifikation, und dann läuft jeder in einem separaten Prozess. –

Antwort

6

Dies ist ein Auszug aus meinem exrm Projekt, das das im Grunde genau das tut: es findet jedes Modul, das das Plugin Verhalten implementiert:

@doc """ 
    Loads all plugins in all code paths. 
    """ 
    @spec load_all() :: [] | [atom] 
    def load_all, do: get_plugins(ReleaseManager.Plugin) 

    # Loads all modules that extend a given module in the current code path. 
    @spec get_plugins(atom) :: [] | [atom] 
    defp get_plugins(plugin_type) when is_atom(plugin_type) do 
    available_modules(plugin_type) |> Enum.reduce([], &load_plugin/2) 
    end 

    defp load_plugin(module, modules) do 
    if Code.ensure_loaded?(module), do: [module | modules], else: modules 
    end 

    defp available_modules(plugin_type) do 
    # Ensure the current projects code path is loaded 
    Mix.Task.run("loadpaths", []) 
    # Fetch all .beam files 
    Path.wildcard(Path.join([Mix.Project.build_path, "**/ebin/**/*.beam"])) 
    # Parse the BEAM for behaviour implementations 
    |> Stream.map(fn path -> 
     {:ok, {mod, chunks}} = :beam_lib.chunks('#{path}', [:attributes]) 
     {mod, get_in(chunks, [:attributes, :behaviour])} 
    end) 
    # Filter out behaviours we don't care about and duplicates 
    |> Stream.filter(fn {_mod, behaviours} -> is_list(behaviours) && plugin_type in behaviours end) 
    |> Enum.uniq 
    |> Enum.map(fn {module, _} -> module end) 
    end 
+0

Also ist es nur Laufzeit? – Haito

+0

Sie könnten den gleichen Code auch zur Kompilierungszeit ausführen, obwohl es einige Vorbehalte geben kann (ich musste das nicht tun) - im Falle des Falles ist das nicht notwendig. Sie können die Ergebnisse auch bei der ersten Ausführung zwischenspeichern, indem Sie sie in der Anwendungsumgebung speichern. Es hängt wirklich davon ab, wie oft es aufgerufen werden muss. – bitwalker

0

Eine weitere Möglichkeit zu prüfen, welche zu Behaviors ähnlich ist Protocols. Erstellen Sie ein Protokoll, und dann muss jeder neue Befehl dieses Protokoll implementieren.

+0

Ja, ich dachte darüber nach. Aber Protokolle werden pro Typ implementiert (für: Listen usw.). Und hier wird die Signatur jeder Funktion gleich sein. – Haito

0

Ich schrieb eine Bibliothek, die nur Plugins lädt, wenn sie einen bestimmten Funktionsaufruf unterstützen. Verhalten wird in Elixir abgewertet, @behaviour im Erlang-Modus wird immer noch unterstützt, aber kauft viel weniger, als Sie erwarten würden. (Im Grunde nur eine Kompilierzeitwarnung, wenn Sie eine Funktionssignatur verpassen).

https://github.com/bbense/pluginator

+0

Das ist schade, dass es veraltet ist. – Haito

+0

Das Verhalten "Makro" in Elixir ist veraltet, da nur @behaviour-Modulattribute verwendet werden. Die Funktionalität ist immer noch da, nur die Elixir-Verpackung wurde entfernt, da entschieden wurde, dass nichts hinzugefügt wurde, das nützlich genug wäre, um es zu rechtfertigen. –

Verwandte Themen