Access to defined functions of the current module

In this example:

defmodule Foo do
  defmacro __using__(_) do
    # How to know here the methods defined in Foo / __MODULE__ ?
  end
end

defmodule Bar do
  def bar(a, b), do: a + b
  use Foo
end

My intention is using it with __before_compile__ and defining functions with similar names and arities; i.e. Foo could define bar_debug/2, call bar/2 and print stuff.

Thank you!

You can’t. The necessary information is only created when the module is compiled. Lets say its the very last __before_compile__ that gets run (simplification!).

If you want to gather additional information about what has defed so far, you can use @on_defintion and do some book-keeping on your own in an ETS or maybe in the PD.

2 Likes

This is an interesting limitation. I suppose I can solve it using @on_definition but it won’t be so elegant (or magic).

Thank you for your time.

Only a limitation in that the information does not actually exist yet. ^.^;

Maybe it is because my Ruby background and I don’t have much idea of Elixir/Erlang internals, but if the macro def has been already called the information is somewhere. Maybe it is complicated to expose or maybe it is not done because it will be a bad practice (just guessing). For sure the module it does not exist already, but it is being defined.

In fact, if I would redefine the def macro I could do a similar result, but in my case, it seems overengineering.

Well the thing about def is that it is like doing this in another ‘form’:

[ 1+2
, 3+4
, some_function()
, 5+6 
]

What you are conceptually asking for is for the some_function() to know the previous values in the elements before it in a list (which you could only do by passing it into the function, which of course you could do to the use call too, that is explicit, explicit is good).

Less conceptually and more ‘how it is really done’, the do ... end body of a defmodule just creates a literal list of the abstract code, like this:

iex(1)> q = quote do
...(1)> defmodule Blah do
...(1)>   def bloop, do: 42
...(1)>   def add(a,b), do: a+b
...(1)> end
...(1)> end
{:defmodule, [context: Elixir, import: Kernel],
 [
   {:__aliases__, [alias: false], [:Blah]},
   [
     do: {:__block__, [],
      [
        {:def, [context: Elixir, import: Kernel],
         [{:bloop, [context: Elixir], Elixir}, [do: 42]]},
        {:def, [context: Elixir, import: Kernel],
         [
           {:add, [context: Elixir], [{:a, [], Elixir}, {:b, [], Elixir}]},
           [
             do: {:+, [context: Elixir, import: Kernel],
              [{:a, [], Elixir}, {:b, [], Elixir}]}
           ]
         ]}
      ]}
   ]
 ]}

Do you see the calls there. When you do something like use a module, it does this:

iex(2)> defmodule Testering do
...(2)>   defmacro __using__(arg) do   
...(2)>     IO.inspect(arg, label: :Using)
...(2)>     :hello_world
...(2)>   end
...(2)> end
{:module, Testering,
 <<70, 79, 82, 49, 0, 0, 4, 108, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 158,                                                     
   0, 0, 0, 15, 16, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,                                                        
   114, 105, 110, 103, 8, 95, 95, 105, 110, 102, ...>>, {:__using__, 1}}
iex(3)> q = quote do                      
...(3)> defmodule Blah do                 
...(3)>   use Testering                   
...(3)>   def bloop, do: 42               
...(3)>   def add(a,b), do: a+b           
...(3)> end                               
...(3)> end
{:defmodule, [context: Elixir, import: Kernel],
 [
   {:__aliases__, [alias: false], [:Blah]},
   [
     do: {:__block__, [],
      [
        {:use, [context: Elixir, import: Kernel],
         [{:__aliases__, [alias: false], [:Testering]}]},
        {:def, [context: Elixir, import: Kernel],
         [{:bloop, [context: Elixir], Elixir}, [do: 42]]},
        {:def, [context: Elixir, import: Kernel],
         [
           {:add, [context: Elixir], [{:a, [], Elixir}, {:b, [], Elixir}]},
           [
             do: {:+, [context: Elixir, import: Kernel],
              [{:a, [], Elixir}, {:b, [], Elixir}]}
           ]
         ]}
      ]}
   ]
 ]}

See how the ‘use’ call is still just AST, when it is expanded later (pre-compilation) each node in the AST will be checked if it is a macro, if so the macro is executed with the ast of it’s arguments and the result will replace it’s node in the AST. Consequently since the AST is just a big list of tuples and such it does not know what it’s prior or after values are, just like the above list example with 1+2 and so forth. :slight_smile:

If you want to know what functions actually end up defined at compilation then you can use the after_compile hook, but at that point you cannot edit it easily (though you could cause another recompilation with new state). The on_definition hook is generally the best way, but even then macro’s or other stuff can get involves that makes it less accurate too.

Instead of trying to transform a module of functions it is instead better to reformulate the problem. I.E. instead of asking ‘how’ to do something instead ask ‘what is the end goal of what you are trying to accomplish’, see this. :slight_smile:

3 Likes

Thank you so much for the free master class :slight_smile: I understood perfectly :ok_hand:

Because you take your time explaining this, I’ll explain my original problem.

I have a project with more than a hundred “commands”. A command in the project is an unit of change, ie RegisterUser, PublishPost, etc. All those commands have a function call which receives a different number of arguments:

RegisterUser.call(%{name: "asdf", email: "..."})
PublishPost.call(user, %{body: ""})

Now I have to send events after the execution of each command. Depending on the command result it should send one or another: UserRegisteredEvent, UserRegistrationFailed, etc.

So my idea is basically to change the old functions call to do_call and inserting a new generic call using the Command module:

def call(input1, input2) do
  do_call(input1, input2)
  |> publish_events()
end

def publish_events(result) do
  spawn(fn -> 
    generate_events(result)
    |> send_events()
  )
  result
end

def generate_events(_), do: []

Now, the only thing I have to do in each command is to change the call to do_call and define generate_events for each possible result.

I wanted to detect the do_call function arity to define the call dinamicaly.

At the moment I’m opting for a more explicit solution and changing the call functions to arity 1.

However, I was (wrongly) pretty sure if it was possible to do this…

Explaining all this whole situation is much more difficult to explain, so I chose to a simpler and more direct question.

Thank you for your time. I learned a lot @OvermindDL1!

Considering the events can have different arguments, I’d make them be struct’s, like a RegisterUser struct and so forth. Then you can just pass it to your do_call or so (you can even have the callback be ‘on’ the structs module if you wish), or completely genericize it by passing it to a protocol (not really needed in this case overall to be honest). And to send the event I’d need to see what kind of responses you can have, but they should really be generic as well, say {:ok, someStruct} for successful results to send events and {:error, reason} for do not send or failures or so. Depending on how the events work just using a pubsub thing in elixir would probably be all around easier, but it depends on how it is used and what you have planned for it. :slight_smile:

1 Like