Reputation: 922
I'm pretty new to Elixir and functional programming-languages in general.
In Elixir, I want to call one specific function on Modules, given the Module name as String.
I've got the following (very bad) code working, which pretty much does what I want to:
module_name = elem(elem(Code.eval_file("module.ex", __DIR__), 0), 1)
apply(module_name, :helloWorld, [])
This (at least as I understand it) compiles the (already compiled) module of module.ex
in the current directory. I'm extracting the modules name (not as a String, don't know what data-type it actually is) out of the two tuples and run the method helloWorld
on it.
There are two problems with this code:
It prints a warning like redefining module Balance
. I certainly don't want that to happen in production.
AFAIK this code compiles module.ex
. But as module.ex is already compiled and loaded, it don't want that to happen.
I don't need to call methods on these modules by filename, the module-name would be ok too. But it does have to by dynamic, eg. entering "Book" at the command line should, after a check whether the module exists, call the function Book.helloWorld
.
Thanks.
Upvotes: 25
Views: 13472
Reputation: 1823
Here is a simple explanation:
Assuming that you have a module like this:
defmodule MyNamespace.Printer do
def print_hello(name) do
IO.puts("Hello, #{name}!")
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!
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.
Upvotes: 6
Reputation: 5962
Note that you always need to prefix your modules with "Elixir."
defmodule Test do
def test(text) do
IO.puts("#{text}")
end
end
apply(String.to_existing_atom("Elixir.Test"), :test, ["test"])
prints "test" and returns {:ok}
Upvotes: 14
Reputation: 15293
Also note that the name of a module is an atom so doing String.to_existing_atom
isn't usually needed. Consider this code:
defmodule T do
def first([]), do: nil
def first([h|t]), do: h
end
In this case you can simply do the apply in this fashion:
apply(T,:first,[[1,2,3]])
#=> 1
Or this example (List below is the Elixir List module):
apply(List,:first,[[1,2,3]])
#=> 1
I mean to say that if you know the name of the module, it's not necessary to pass it as a string and then convert the string to an existing atom. Simply use the name without quotation marks.
Upvotes: 4
Reputation: 922
Well, thats where asking helps: You'll figure it out yourself the minute you ask. ;)
Just using apply(String.to_existing_atom("Elixir.Module"), :helloWorld, [])
now. (maybe the name "Module" isn't allowed, don't know)
Upvotes: 29