Nathan Long
Nathan Long

Reputation: 126072

Does Elixir support introspection to show function origins?

If a module imports multiple other modules, it may not be obvious where a given function came from. For example:

defmodule Aimable do
  import Camera
  import Gun

  def trigger do
    shoot # which import brought it in?
  end
end

I know there are ways to minimize this kind of confusion: good naming, focused modules, targeted imports like import Gun, only: [:shoot], etc.

But if I come across code like this, is there a way to inspect Aimable and see the origins of the function shoot?

Upvotes: 6

Views: 734

Answers (4)

Nathan Long
Nathan Long

Reputation: 126072

Use __ENV__

As I learned on another question, the __ENV__ macros gives access to various environment info, including __ENV__.functions and __ENV__.macros.

__ENV__.functions returns a list of tuples of modules and the list of functions they provide, like:

[{Some.module, [do_stuff: 2]}, {Other.Module, [a_function: 2, a_function: 3]}]

You could visually scan this for shoot, or write code to search through it.

Upvotes: 3

Onorio Catenacci
Onorio Catenacci

Reputation: 15343

Another potential technique (added for sake of completeness):

defmodule Aimable do
  import Camera, :only [shoot: 0]
  import Gun

#etc.

or

defmodule Aimable do
  import Camera
  import Gun, :only [shoot: 0]

# etc.

Then see which one won't compile correctly.

Just another way to accomplish this end.

Upvotes: 0

coderVishal
coderVishal

Reputation: 9109

You can do this directly:

# from inside the module; IO.inspect(&Aimable.shoot/0) reveals nothing
IO.inspect &shoot/0 #=> &Gun.shoot/0

Check this out

Also remember that you cannot have same function names with same arity in two different modules and import them both in another module. This will result in ambiguity error when calling that function.

Another painful way. You can use function_exported?/3.. Specs:

function_exported?(atom | tuple, atom, arity) :: boolean

Returns true if the module is loaded and contains a public function with the given arity, otherwise false.

Examples:

function_exported?(Gun,    :shoot, 0) #=> true
function_exported?(Camera, :shoot, 0) #=> false

Upvotes: 6

AbM
AbM

Reputation: 7779

I am using Elixir 1.1.0 and what you are describing does not seem to be allowed. Here is the script (in aimable.ex):

defmodule Gun do
  def shoot do
    IO.puts "Gun Shot"
  end
end

defmodule Camera do
  def shoot do
    IO.puts "Camera Shot"
  end
end

defmodule Aimable do
  import Camera
  import Gun

  def trigger do
    shoot
  end
end

Now when I run iex aimable.ex, I get a CompileError

Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

** (CompileError) aimable.ex:18: function shoot/0 imported from both Camera and Gun, call is ambiguous (elixir) src/elixir_dispatch.erl:111: :elixir_dispatch.expand_import/6 (elixir) src/elixir_dispatch.erl:82: :elixir_dispatch.dispatch_import/5

Upvotes: 1

Related Questions