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

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]

Related

How to reduce the complexity and automate my approach for building a struct?

I have the following macro and it should allow me to build a struct with one or 2 arguments only!
macro baseStruct(name, arg)
if length(arg.args)==2 && isa(arg.args[1],Symbol) || length(arg.args)==1
aakws = Pair{Symbol,Any}[]
defaultValues=Array{Any}(nothing,1)
field_define(aakws,defaultValues,1,arg,first=true)
:(struct $name
$(arg.args[1])
function $name(;$(arg.args[1])=$(defaultValues[1]))
new(check($name,$(arg.args[1]);$aakws...))
end
end)
else length(arg.args)==2 && !isa(arg.args[1],Symbol)
aakws1 = Pair{Symbol,Any}[]
aakws2 = Pair{Symbol,Any}[]
defaultValues=Array{Any}(nothing,2)
field_define(aakws1,defaultValues,1,arg)
field_define(aakws2,defaultValues,2,arg)
:(struct $name
$(arg.args[1].args[1])
$(arg.args[2].args[1])
function $name(;$(arg.args[1].args[1])=$(defaultValues[1]),$(arg.args[2].args[1])=$(defaultValues[2]))
new(check($name,$(arg.args[1].args[1]);$aakws1...),check($name,$(arg.args[2].args[1]);$aakws2...))
end
end)
#baseStruct test(
(arg1,(max=100.0,description="this arg1",min=5.5)),
(arg2,(max=100,default=90))
)
The macro will expand to:
struct test
arg1
arg2
end
and the following instance:
test1=test(arg1=80.5)
should give:
test1(arg1=80.5,arg2=90)
#check_function returns the argument. It allows me to check the argument in a specific way.
check(str,arg;type=DataType,max=nothing,min=nothing,default=nothing,description="")
#do somthing
return arg
end
#field_define_function it takes the second parameter from the macro as Expr. and convert it to a array with Pair{Symbol, Any} and then assign it to aakws.
I can extend this Code for more arguments, but as you can see it will be so long and complex. Have anybody a tip or other approach to implement this code for more/unultimate arguments in a more efficient way?
I don't have the code for field_define or check, so I can't work your example directly. Instead I'll work with a simpler macro that demonstrates how you can splat-interpolate a sequence of Expr into a quoted Expr:
# series of Expr like :(a::Int=1)
macro kwstruct(name, arg_type_defaults...)
# :(a::Int)
arg_types = [x.args[1]
for x in arg_type_defaults]
# :a
args = [y.args[1]
for y in arg_types]
# build :kw Expr because isolated :(a=1) is parsed as assignment
arg_defaults = [Expr(:kw, x.args[1].args[1], x.args[2])
for x in arg_type_defaults]
# splatting interpolation into a quoted Expr
:(struct $name
$(arg_types...)
function $name(;$(arg_defaults...))
new($(args...))
end
end
)
end
This macro expands #kwstruct D a::Int=1 b::String="12" to:
struct D
a::Int
b::String
function D(; a = 1, b = "12")
new(a, b)
end
end
And you can put as many arguments as you want, like #kwstruct(F, a::Int=1, b::String="12", c::Float64=1.2).
P.S. Splatting interpolation $(iterable...) only works in quoted Expr, which look like :(___) or quote ___ end. If you're constructing with an Expr() call, just use splatting:
vars = (:b, :c)
exq = :( f(a, $(vars...), d) ) # :(f(a, b, c, d))
ex = Expr(:call, :f, :a, vars..., :d) # :(f(a, b, c, d))
exq == ex # true

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"

Unquote the result of a function defined by a macro

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.

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