Execute dynamic code in Elixir

3 years ago

Sometimes you might need to execute code dynamically based - for instance - on the name of a module, akin to code reflection on other languages.

In Elixir one way this can be achieved is by using Erlang's :code module. As an example, say you want to run a function in a module named MyProject.Foo.

Find that module

The first thing we need to do is to find the module:

def find_module(name) do
    :code.all_loaded()
    |> Enum.map(&elem(&1, 0))
    |> Enum.find(fn module ->
      module_name = Atom.to_string(module)

      String.starts_with?(module_name, "Elixir.#{name}") and
        !String.ends_with?(module_name, "Test")
    end)
 end

You might notice two things there: 1. all modules in Elixir start with Elixir. and 2. i am excluding a potential test module that might have the same name as the module we want to run the code on.

Run them codes

Now we need to be able to execute functions on our module. For that we can use the apply function from the Kernel:

case find_module(module_name) do
  nil -> {:error, "module #{module_name} not found"}
  module -> apply(module, function, args)
end

A concrete example

defmodule MyProject.Foo do
  def bar(message) do
    IO.puts("Hello #{message}")
  end
end

defmodule MyProject.Runtime do
  def find_module(name) do
    :code.all_loaded()
    |> Enum.map(&elem(&1, 0))
    |> Enum.find(fn module ->
      module_name = Atom.to_string(module)

      String.starts_with?(module_name, "Elixir.#{name}") and
        !String.ends_with?(module_name, "Test")
    end)
  end
  
  def execute_module_function(module_name, function, args \\ []) do
    case find_module(module_name) do
      nil -> {:error, "module #{module_name} not found"}
      module -> apply(module, function, args)
    end
  end
end

MyProject.Runtime.execute_module_function("MyProject.Foo", :bar, ["world"])
MyProject.Runtime.execute_module_function("MyProject.NonExisting", :bar, ["world"])
Happy coding!