Abstract: In Elixir applications, some times we want to dynamically call functions in modules when the function names and module names are sent as strings or atoms, or configured in some place as strings or atoms.
Here is how we can do it.
Assuming that you have a module like this:
defmodule MyNamespace.Printer do
defstruct name: "Unknown name", type: "Unknown type"
def print_hello(name) do
IO.puts("Hello, #{name}!")
end
def print_list(message, list) do
IO.puts("#{message}: #{inspect(list)}")
end
def print_info(printer) do
"#{printer.name} - #{printer.type}"
end
end
Then you have a string that hold the module name that you can pass around in your application like this:
module_name = "Elixir.MyNamespace.Printer"
Whenever you receive the string module_name, you can instantiate a module and call functions on the module like this:
module = String.to_existing_atom(module_name)
module.print_hello("John")
It will print:
Hello, John!
Another way to dynamically call the function MyNamespace.Printer.print_hello/1 is:
print_hello_func = &module.print_hello/1
print_hello_func.("Jane")
It will print:
Hello, Jane!
Or if you want to have both module_name as atom and function_name as atom, and pass them around to be executed somewhere, you can write something like this:
a_module_name = :"Elixir.MyNamespace.Printer"
a_function_name = :print_hello
Then whenever you have a_module_name and a_function_name as atoms, you can call like this:
apply(a_module_name, a_function_name, ["Jackie"])
It will print
Hello, Jackie!
Or more complex arguments:
apply(module, :print_list, ["Here is a good list", [1, 2, 3]])
It will print:
Here is a good list: [1, 2, 3]
Of course you can pass module_name and function_name as strings and convert them to atoms later. Or you can pass module name as atom and function as a reference to function.
You can dynamically create a struct from the module like this:
printer = %{struct(module)| name: "Samsung", type: "Color Laser printer"}
It will create:
%MyNamespace.Printer{name: "Samsung", type: "Color Laser printer"}
You can get back the module from a struct like this:
module = printer.__struct__
It will give you back
MyNamespace.Printer