Unquote the result of a function defined by a macro - macros

I have the following definition of my macro
defmacro defverified(sign, body) do
{name, _, [param]} = sign
quote do
def unquote(name)(unquote(param)) do
unquote(param) = verify! param
unquote(body)
end
end
end
Were verify!/1 returns its paramters if it is verified as a correct parameter
And my function is defined as follows
defverified toto(p) do
IO.inspect p
end
And the inspection of the content of p is correct but the return of the function is a quoted form of my variable.
iex(3)> res = Toto.toto(1)
1
[do: 1]
iex(4)> res
[do: 1]
Is it possible to have an unquoted form for the return of my function or should I unquote it manually?
I expect the following output of my function
iex(3)> res = Toto.toto(1)
1
1
iex(4)> res
1

This happens because the do...end construct is a peculiar piece of syntactic sugar. For example, this:
def toto(p) do
IO.inspect p
end
is equivalent to:
def toto(p), do: IO.inspect p
which, because keywords at the end of an argument get passed as a keyword list, is equivalent to:
def(toto(p), [do: IO.inspect(p)])
Since your defverified macro only expects a do block and no other keyword parameters, we can explicitly match out the actual body:
defmacro defverified(sign, [do: body]) do
...
Before doing this, the last form in the toto function would literally be:
[do: IO.inspect param]
which would call IO.inspect as expected but then return a keyword list instead of a plain value.

Related

is it possible to execute a code inside an expression?

I have this macro:
macro superM(name, field)
:(struct $(esc(name))
map(x -> (for i = 1:length(x) return $(esc(x[i][1])) end),field.args)
end)
end
#superM test ((arg1,2),(arg2,4))
I just want to map the second argument to get the arguments for the struct in the macro.
So, this macro should define a struct like this:
struct test
arg1
arg2
end
As I understand that I'm writing the map function inside an expression, that means my code will not execute and the map function will be a part of my struct. But is there any way to execute the map function inside the expression?
Here I got error: LoadError: UndefVarError: x not defined
You could try:
macro superM(name, field)
code = Base.remove_linenums!(quote
struct $name
end
end)
append!(code.args[1].args[3].args,[f.args[1] for f in field.args])
code
end
Now give it a spin:
julia> #macroexpand #superM(mysss,((field1,1),(field2,2)))
quote
struct mysss
field1
field2
end
end
julia> #superM(mysss,((field1,1),(field2,2)))
julia> mysss(1,5)
mysss(1, 5)
julia> dump(mysss(1,"ala"))
mysss
field1: Int64 1
field2: String "ala"

Why does one of these semingly equivalent macros fail?

Consider these two macro definitions:
macro createTest1()
quote
function test(a = false)
a
end
end |> esc
end
macro createTest2()
args = :(a = false)
quote
function test($args)
a
end
end |> esc
end
According to the builtin Julia facilities they should both evaluate to the same thing when expanded:
println(#macroexpand #createTest1)
begin
function test(a=false)
a
end
end
println(#macroexpand #createTest2)
begin
function test(a = false)
a
end
end
Still I get a parse error when trying to evaluate the second macro:
#createTest2
ERROR: LoadError: syntax: "a = false" is not a valid function argument name
It is a space in the second argument list. However, that should be correct Julia syntax. My guess is that it interprets the second argument list as another Julia construct compared to the first. If that is the case how do I get around it?
The reason that the second macro is failing as stated in my question above. It looks correct when printed however args is not defined correctly and Julia interprets it as an expression which is not allowed. The solution is to instead define args according to the rules for function parameters. The following code executes as expected:
macro createTest2()
args = Expr(:kw, :x, false)
quote
function test($(args))
a
end
end |> esc
end

How to dynamically generate a Phoenix controller function with a map argument?

So I'm using Phoenix 1.3, and I created a macro to generate a function and inject it into a controller.
Based on the number I pass in, I want it to generate a map with that many parameters that are named "id1", "id2", etc. all the way up to "id#{number}". This map will be part of the argument list along with the usual Phoenix "conn".
So I want to generate a method like this to be pattern matched against and "some stuff" can be executed:
def index(conn, %{"id1" => id1, "id2" => id2}) do
# some stuff
end
when I call the macro create_some_function_by_number("index", 2).
My macro looks something like:
defmacro create_some_function_by_number(name, num) do
params =
for n <- 1..num, do: %{"id#{n}" => Macro.var(:"id#{n}", nil)}
|> Map.new
quote do
def unquote(:"#{name}")(unquote(Macro.escape(params)) do
# some code here for the index action
end
end
end
1) How do I inject the "conn" into the function head so it can be pattern matched against?
2) Is this the correct way to create the map to be pattern matched against?
While you can definitely use macros in this way, you probably should not. Here is a working solution with comments:
defmodule MyMacro do
defmacro create_some_function_by_number(name, num, do: block) do
params =
for n <- 1..num do
{"id#{n}", Macro.var(:"id#{n}", nil)}
end
# We can't call Macro.escape because it is for escaping values.
# In this case, we have a mixture of values "id#{n}" and
# expressions "Macro.var(...)", so we build the map AST by hand.
pattern =
{:%{}, [], params}
conn =
Macro.var(:conn, nil)
quote do
def unquote(:"#{name}")(unquote(conn), unquote(pattern)) do
unquote(block)
end
end
end
end
defmodule MyExample do
import MyMacro
create_some_function_by_number :index, 2 do
{conn, id1 + id2}
end
end
IO.inspect MyExample.index(:conn, %{"id1" => 1, "id2" => 2})
As you can see, macros can make the code harder to understand. If you can solve it at runtime, it should definitely be preferred.

Elixir Macros: Get unquoted parameter value outside of quote

I have a macro which gets a module name as parameter and I want to call a function on that module to get some data in order to generate the quote block.
Example:
defmacro my_macro(module) do
data = apply(module, :config, [])
# do something with data to generate the quote do end
end
Obviously, this doesn't work because the parameter value is quoted. I could fetch the data inside the quote block and act accordingly but that would put the whole logic inside the module that uses my macro which is quite dirty. I want to inject as little code as possible.
You can extract the module out by pattern matching with its quoted form: {:__aliases__, _, list} where list is a list of atoms which when concatenated with a dot (use Module.concat/1) produces the full module name.
defmodule A do
defmacro my_macro({:__aliases__, _, list}) do
module = Module.concat(list)
module.foo()
end
end
defmodule B do
def foo do
quote do
42
end
end
end
defmodule C do
import A
IO.inspect my_macro B
end
Output:
42

How to “splat” the function arguments for dynamically created function

I am generating some functions in the module:
defmodule M do
funs = [do_it: [:arg1, :arg2]]
Enum.each(funs, fn {name, args} ->
args = Enum.map(args, & {&1, [], Elixir})
def unquote(name)(unquote(args)),
do: IO.inspect(unquote(args))
end)
end
The issue is the generated function obviously accepts one single argument, namely a list of size 2:
▶ M.__info__(:functions)
#⇒ [do_it: 1]
The goal is to dynamically declare the function accepting two arguments. In ruby terminology, it would be to unsplat argument list.
Is there a possibility to accomplish this without pattern matching the resulting AST for {:do_it, blah, [[ list of arguments ]]} and flattening the list manually?
You can use Kernel.SpecialForms.unquote_splicing/1 to "splice" in the args list:
defmodule M do
funs = [do_it: [:arg1, :arg2], do_it: [:arg1, :arg2, :arg3]]
Enum.each(funs, fn {name, args} ->
def unquote(name)(unquote_splicing(args)), do: :ok
end)
end
iex(1)> M.__info__(:functions)
[do_it: 2, do_it: 3]