I'm trying to write the macro that generates a module:
defmodule GenModules do
defmacro module(name, do: body) do
quote do
defmodule unquote(String.to_atom(name)) do
unquote(body)
end
end
end
end
What I'm missing is to how to inject the 'import' statement that would refer the module the macro will be called from?
I.e., the following code:
defmodule Test do
import GenModules
module "NewModule" do
def test_call do
hello_world
end
end
def hello_world do
IO.puts "Hello"
end
end
won't compile because hello_world function is not visible from the generated NewModule.
So I need to generate
import Test
before the body of the module, somehow getting the name of the module the macro was called from. How do I do this?
Thank you,
Boris
To get the module your macro was called from you can use the special form __CALLER__. It contains a bunch of information, but you can extract the calling module like so:
__CALLER__.context_modules |> hd
But more generally I don't think what you want is possible - you cannot import a module before you finish defining it. For example:
defmodule A do
defmodule B do
import A
end
end
results in an error:
** (CompileError) test.exs:3: module A is not loaded but was defined.
This happens because you are trying to use a module in the same context it is defined.
Try defining the module outside the context that requires it.
Related
I am currently experimenting with defining and using my own slightly adjusted receive macro, based on its default implementation. As a classic example, let’s say I want to log every time a Process starts receiving a message from the mailbox. Could I define my own version of the receive macro that does the logging, then calls/uses the default receive macro and import this custom receive into my existing code?
Below is a not-working example to better illustrate what I am trying to achieve:
defmodule MyWeirdReceive do
def receive(args) do
IO.puts "I just started receiving a message from the mailbox"
Kernel.SpecialForms.receive(args)
end
end
defmodule Stack do
import Kernel, except: [receive: 1]
import MyWeirdReceive
def loop(state, ctr) do
receive do
{_from, :push, value} ->
loop([value | state], ctr + 1)
{from, :pop} ->
[h | t] = state
send(from, {:reply, h})
loop(t, ctr)
end
loop(state, ctr)
end
end
It's hard to override Kernel.SpecialForms macros because you can't import them like you could macros from anywhere else (you get a CompileError). I think your best bet is to name your macro something other than "receive".
Other problems you may be running into:
You are defining your receive as a function instead of a macro. Elixir will try evaluating your do/after block as a literal keyword list, and that won't work well.
receive/1 comes from Kernel.SpecialForms, not Kernel, so your import/:except won't work.
I have a module called Interfaces:
defmodule Interfaces do
defmacro __using__(module) do
#module unquote(module) |> List.first |> elem(1)
defmacro expose(name) do
IO.inspect params, label: "Expanding macro with: "
IO.inspect #module, label: "I need this !!"
def unquote(name)() do
IO.puts "The function has been created with exposed name"
end
end
end
end
Another module called Interfaces.MyModule :
defmodule Interfaces.MyModule do
use Interfaces, for: Something.DefinedElseWhere
expose :plop
end
But at compile time I get
** (CompileError) lib/my_module.ex:6: undefined function expose/1
I highly recommend that you read the Macros Guide on the official Elixir website. While what you're doing is possible (using quote), it is not encouraged at all.
Macros should be simple and their functionality should be broken down further in other macros and methods if the need arises. One way to do that is to use the import statement in your macro to import other macros that you need exposed in the final module:
defmodule Interface do
defmacro __using__(opts) do
quote(bind_quoted: [opts: opts]) do
import Interface
#interface_module Keyword.get(opts, :for)
end
end
defmacro expose(name) do
quote do
IO.inspect #interface_module, label: "I need this !!"
def unquote(name)() do
IO.puts "The function has been created with exposed name"
end
end
end
end
Now you can use it:
defmodule MyImplementation do
use Interface, for: AnotherModule
expose(:hello)
end
Here's another example from one of my projects, on how you should break down the implementation of large macros using helper functions and other macros.
I'm currently working on some elixir macro fun stuff. I have a module like that:
defmodule MapUtils do
#handlers "I don't want you"
defmacro __using__(_) do
quote do
import MapUtils
Module.register_attribute __MODULE__, :handlers, accumulate: true
#handlers "I want you"
end
end
defmacro test do
IO.inspect #handlers
quote do
IO.inspect(#handlers)
end
end
end
defmodule Test do
use MapUtils
def testowa do
MapUtils.test
end
end
Test.testowa
Which yields in such result:
"I don't want you"
["I want you"]
I want to access #handlers from callers module outside of the quote block to generate some code based on what it is. As far as my understanding goes first inspect is being executed and second is being transformed to AST and executed in differnt context.
Is there a way to get access to that #handlers from caller module in compile time?
If I understand your question correctly, you want this:
Module.register_attribute __MODULE__, :handlers,
accumulate: true,
persist: true
before the change:
iex(6)> Test.module_info(:attributes)
[vsn: [95213125195364189087674570096731471099]]
after the change:
iex(13)> Test.module_info(:attributes)
[vsn: [95213125195364189087674570096731471099], handlers: ["I want you"]]
I stumbled upon this.
I realized I can call it with __CALLER__ like that:
Module.get_attribute(__CALLER__.module, :handlers)
And in fact it returned value it suppose to return.
I want to generate a module which using a macro created function, everything works as expected but except for the context on unquote(block) seems works out of expectation.
The testing code are the following.
defmodule A do
defmacro wrap(head,opts \\[],do: block) do
{name,args} = case head do
name when is_atom(name) ->
{name,[]}
h ->
Macro.decompose_call h
end
quote do
def unquote(name)(unquote_splicing(args)) do
IO.inspect __ENV__
unquote(block)
end
end
end
end
defmodule B do
import A,only: [wrap: 2]
alias B.C
wrap test do
C.test2
end
end
defmodule C do
def test do
quote do
import A,only: [wrap: 2]
alias B.C
wrap test do
IO.inspect __ENV__
C.test2
end
end
end
end
defmodule B.C do
def test2 do
"good"
end
end
Module.create D,C.test,__ENV__
module B should be identical to D,except for D is a dynamically generated module and B is a predefined module.
When calling B.test, it correctly resolves C to B.C and returning back "good" as result.
But when calling D.test, it raised out exception where C cannot being found((UndefinedFunctionError) undefined function C.test2/0)
Is there anyone who have some insights on the root of the problem here? Any help would be real appreciated. Thx in advance for the help ;)
Update
Confirmed as a bug and is fixed
Essentially, the quote block is not properly resolving the alias B.C. If you directly refer to it inside of the block, the problem evaporates. I'm not exactly sure why this is the behavior. It is possible that the alias with the same current module name makes the call ambiguous. Either way, you can fix it by rewriting your C module to the following:
defmodule C do
def test do
quote do
import A, only: [wrap: 2]
wrap test do
IO.inspect __ENV__
B.C.test2
end
end
end
end
Here's the result:
iex(1)> D.test
"good"
** Update:
This alias issue does appear to be a bug. With the alias inserted, the macro printout shows that both C and B.C are listed as aliases and yet it still doesn't work. I also verified that the current module name was not causing a collision issue.
I have just started learning Elixir and I am unable to figure out how import works in Elixir.
When I import a module into another module using import I am unable to call the imported function by using the module in which it is imported.
But I am able to call function of imported module inside function in the module which it was imported.
defmodule A do
def func do
IO.puts("func called")
end
end
defmodule B do
import A
end
A.func # o/p: "func called"
B.func # (UndefinedFunctionError) undefined function: B.func/0
defmodule B do
import A
def func2 do
func
end
end
B.func2 # o/p "func called"
I am unable to figure out why B.func not works while I was able to call func from func2. Is there some kind of theory that I am missing. Coming from the Ruby background this behaviour looks odd to me. Please can anybody help me out or point me to some good resource to read.
import does not really import anything in the way many other languages do. All it does is makes the imported module's exported functions accessible from the current namespace. To quote the docs
We use import whenever we want to easily access functions or macros from other modules without using the fully-qualified name.
If you want A.func and B.func to point to the same function you have a couple options. The first is simple - make a wrapper function:
defmodule B do
def func do
A.func
end
end
If you want some more complex inheritance type stuff you can look into creating a __using__ macro with defoverridable and super