Accessing caller module attributes outside of quote do - 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.

Related

Elixir - Extending the default 'receive' macro for a Process

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.

Define another macro inside __using__

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.

GenServer call inside macro

I defined a macro like this:
defmodule Test do
defmacro __using__ do
quote do
require Test
import Test
end
end
defmacro my_macro do
quote do
Gernserver.call() # generic server call from other module
end
end
end
I have this code inside a lib which I am building. When testing this lib in a different application I get a compile error on the file which has the use Test.
Error: exited in: GenServer.call ... exit no process (on the line of the my_macro call, more specifically on the genserver call). As if the genserver was not running, and it is not because it is compile time.
Adding a simple Application.ensure_all_started(:lib_name) to the using macro seems to fix the problem. But then again, at compile time the code shouldn't be running, am I right ? Or is here something I am not seeing ? Maybe I can not use genserver calls on a macro ?
Thanks.
You are calling every in your TestScheduler during compilation stage.
One should either put all the schedule initialization code inside a function, that would be called explicitly during run stage, or simply
introduce a new GenServer, add it to supervised list (to make Elixir to run it automatically on app start,) and perform this every initialization in it’s start_link (or any other function that would be called later explicitly):
defmodule Test.Scheduler do
use GenServer
use Cronex.Scheduler
def start_link ... do
every :minute do
IO.puts "every minute job"
end
every :minute do
IO.puts "every minute job 2"
end
end
end
Another way round (if you want to keep the file above as clean as possible,) would be to collect data in every macro, and to spawn all the calls during run stage.
To become more familiar with stages, put the following code into /tmp/test.ex:
defmodule A do
IO.puts "Here “every” macro was called: compilation"
def a do
IO.puts "Is not called during compilation stage"
end
end
IO.puts "=== compilation finished ==="
A.a
And issue elixir /tmp/test.ex:
Here “every” macro was called: compilation
=== compilation finished ===
Is not called during compilation stage

Elixir dynamic generated mod with macro created function,problems on resolving the context

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.

Elixir: generating module with proper import

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.