How to alias a module in __using__

Hello, I want to load a alias under my __using__, but with opts help like this:

defmacro __using__(opts) do
  quote(bind_quoted: [opts: opts]) do
    behaviour = Keyword.get(opts, :behaviour)
    alias behaviour
  end
end

It shows me invalid argument for alias, expected a compile time atom or alias, got: behaviour error, if I use alias unquote(behaviour), it shows me unquote called outside quote.

It should be noted, for @behaviour it works if I do like this, @behaviour behaviour but for alias doesn’t work.
How can I fix this? Thanks

1 Like

You‘ll need to pull out the module in the macro body and not in the quoted return. It needs to look like this:

alias unquote(module)

Anything where the alias macro doesn‘t receive the module name directly, but e.g. the ast of a variable won‘t work.

2 Likes

To be honest, I did not understand, I know it doesn’t work even with unquote

this is whole my code:

defmacro __using__(opts) do
    quote(bind_quoted: [opts: opts]) do
      use GenServer, restart: :transient
      require Logger
      alias MishkaInstaller.{PluginState, Hook}
      module_selected = Keyword.get(opts, :module)
      initial_entry = Keyword.get(opts, :initial)
      behaviour = Keyword.get(opts, :behaviour)
      event = Keyword.get(opts, :event)

      @ref event
      alias unquote(behaviour) # doesn't work
      @behaviour behaviour

      # Start registering with Genserver and set this in application file of MishkaInstaller
      def start_link(_args) do
        GenServer.start_link(unquote(module_selected), %{id: "#{unquote(module_selected)}"}, name: unquote(module_selected))
      end

      def init(state) do
        {:ok, state, 300}
      end

      # This part helps us to wait for database and completing PubSub either
      def handle_info(:timeout, state) do
        if is_nil(Process.whereis(MishkaHtml.PubSub)) do
          {:noreply, state, 100}
        else
          unquote(module_selected).initial(unquote(initial_entry))
          {:noreply, state}
        end
      end
    end
  end

If I understand a little about what you said, I can not get module name from a list in using and pass it in alias ! Yes!?, so what is your suggestion in this code!!

One thing people usually have a hard time to grok is that macros only deal with AST – or “code”. Even if things look like a variable they don’t really represent the value assigned to the variable.

defmacro __using__(opts) do
  quote(bind_quoted: [opts: opts]) do
    behaviour = Keyword.get(opts, :behaviour)
    alias behaviour
  end
end

Doing this means alias (also a macro) will receive the AST for the behaviour variable (something like this: {:behaviour, [], Elixir}). So it has no idea what module it’s supposed to alias.

defmacro __using__(opts_ast) do
  behaviour = select_module_from_opts(opts_ast)
  quote do
    alias unquote(behaviour)
  end
end

This on the other hand pulls out the module out of the AST your macro received and creates AST (again a representation of code), where alias receives the module as an argument instead of a variable.

The tricky part here is select_module_from_opts, because again opts is AST not necessarily a plain value to consume. Some values represent themselves in AST – no difference between the AST and the value represented – so they’re rather easy to deal with (e.g. many keyword lists with simple values) , but others are not as simple, e.g. maps or anything containing other variables.

6 Likes

Thank you for your complete explanation. The subject is beyond my comprehension, and I’ve refrained from using alias. :rose:

1 Like