How to preventing a module to be used multiple times

Look at the following code

defmodule A do
  defmacro __using__(_) do
    IO.puts "A is being used"
  end
end

defmodule B do
  defmacro __using__(_) do
    IO.puts "B is being used"
    quote do: use A
  end
end

defmodule C do
  use A
  use B
end

The console shows

A is being used
B is being used
A is being used

My question is, since module A has been explicitly used by C, how can I prevent A from being implicitly used again? Or more precisely, In module B, how can I not to use A whenever A has already been used?

In C language, I can do

#ifndef __SOME_MACRO__
#define __SOME_MACRO__
// ...
#endif

Is there any similar approach in Elixir?

2 Likes

Verbose example:

defmodule A do
  defmacro __using__(_) do
    quote do
      case Module.get_attribute(__MODULE__, :use_attempts) do
        nil ->
          Module.register_attribute __MODULE__, :use_attempts, accumulate: false, persist: false
      	  IO.puts "A is being used for the first time"
          Module.put_attribute(__MODULE__, :use_attempts, 1)
      	i when is_integer(i) -> 
          IO.puts "Prevented an attempt ##{i} to reuse the module"
          Module.put_attribute(__MODULE__, :use_attempts, i + 1)
      end
    end
  end
end

defmodule B do
  defmacro __using__(_) do
    IO.puts "B is being used"
    quote do: use A
  end
end

defmodule C do
  use A
  use B
end

Results in:

B is being used
A is being used for the first time
Prevented an attempt #1 to reuse the module
4 Likes

For note, a use Blah call is just a require Blah; Blah.__using__([]) call, so this is not asking like in a C way of embedding things, but rather you are asking how to prevent a function from being called twice, it doesn’t make much sense generally, but if you really want to then there are many ways to do so, like simple attributes as the above post shows. :slight_smile:

2 Likes

I think you mean require Blah, not import Blah. :wink:

2 Likes

Yes I did, fixed! ^.^

1 Like

This is kinda ActiveSupport::Concern-ish problem.

If I had 2 modules that work like mixins, say module A and B, and a normal module C which needs functions provided by B. I can do this:

defmodule C do
  use B
end

while B needs functions provided by A, but those functions need to be called in the context of C (because of some magical variables like __MODULE__). I can’t do this because A is not used by C.

defmodule B do
  use A

  defmacro __using__(_) do
    quote do
      def func_from_B do
        func_from_A()
      end
    end
  end
end

Of course I can use A in C, but why should I since func_from_A is not directly called in C?

1 Like

I feel like this is a good time to point out that you should try to avoid using defmacro __using__ unless it is truly necessary. It is a rather unflexible construct.

2 Likes

I end up rolling my own lib, concern.

Thanks to @mudasobwa who gave me the idea.

1 Like

This is to be avoided as much as possible.

3 Likes