shahryarjb
Create sub module with atom name
Hello friends,
I want to create a module inside another module with an atom name:
For example:
defmacro sub_field(name, _type, opts \\ [], do: block) do
ast = register_struct(block, opts)
quote do
defmodule unquote(name) do
unquote(ast)
end
end
end
If I use Module name, it is okey and creates a sub module for me like:
sub_field(Oop, String.t(), enforce: true) do
field(:title, String.t())
field(:fam, String.t())
end
But If I replace Oop with :opp it has invalid module name error.
I tried to convert atom to string and use Macro.camelize() and convert it to atom again ( :Oop), but it does not accept.
Then I print :opp, it just returns :opp in my macro but if I put module name Opp it returns something like this:
{:__aliases__,
[
counter: {MishkaDeveloperToolsTest.GuardedStructTest.TestNestedStruct, 3},
line: 520
], [:Opp]}
now how can use atom to do this?
Thank you in advance
Marked As Solved
Eiji
Take a look at this code for inspiration:
defmodule Example do
def sample(module) when is_atom(module) do
module |> Atom.to_string() |> Macro.camelize() |> String.to_atom()
end
end
module = Example
[Opp, :Opp, :opp]
|> Enum.map(&Example.sample/1)
|> Enum.map(&Module.concat(module, &1))
|> IO.inspect()
# returns: [Example.Opp, Example.Opp, Example.Opp]
Note: I have tried Module.split/1, but it does not support non-Elixir modules (those having Elixir. prefix when changing to String).
Edit: I have forgot about Macro.camelize/1, so I have updated my code. Thanks @zachallaun!
Also Liked
Eiji
Weird… when I try this code:
defmodule MyLib do
defmacro sub_field(name, do: block) do
quote do
defmodule unquote(name) do
unquote(block)
end
end
end
end
defmodule Example do
import MyLib
sub_field :opp do
def sample, do: IO.inspect(__MODULE__)
end
end
:opp.sample()
then everything is working without any problem. module is print properly and there are no warnings or errors …
zachallaun
On my phone, so apologies if some of this is incorrect as I can’t test it, but I’d try the following:
defmacro sub_field(name, type, opts \\ [], do: block) do
ast = register_struct(block, opts)
name =
name
|> Atom.to_string()
|> Macro.camelize()
|> String.to_atom()
quote unquote: false, bind_quoted: [name: name, ast: ast] do
module_name = Module.concat(__MODULE__, name)
defmodule unquote(module_name) do
unquote(ast)
end
end
end
I’d really need to test to be sure, and it can be a little tricky to follow with the nested unquote/bind_quotes bit, but essentially you need to access __MODULE__ in the calling module.
Eiji
Ah, I see … You have tried my example to create a top-level module in another module.
First of all you need to understand something … As same as you can check AST with quote do … end as same you can check how modules are “defined” inside using IO.puts(module).
iex> IO.puts(String)
Elixir.String
iex> IO.puts(Example)
Elixir.Example
iex> IO.puts(:opp)
opp
iex> IO.puts(:Opp)
Opp
With this you should understand why sub_field :Opp do … end does not generates Opp module. However it’s easy to do so. Simply add Elixir. prefix instead of :, so:
defmodule MyLib do
defmacro sub_field(name, do: block) do
quote do
defmodule unquote(name) do
unquote(block)
end
end
end
end
defmodule Example do
import MyLib
sub_field Elixir.Opp do
def sample, do: IO.inspect(__MODULE__)
end
end
Opp.sample()
You can also do that within macro using for example Module.concat/2
iex> Module.concat(Elixir, :Opp)
Opp








