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.
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 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
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 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
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.