I was wondering if it's somehow possible to expand ranges in macro's.
Currently when I try this code:
defmodule Main do
defmacro is_atom_literal(char) do
quote do:
Enum.any?(unquote([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]), &(unquote(char) in &1))
end
def test do
c = 'b'
case c do
c when is_atom_literal(c) ->
:ok
end
end
end
Main.test
I get the error "** (CompileError) test.ex: invalid quoted expression: 97..122". Is it possible to make this idea work?
To fix "invalid quoted expression" you can use Macro.escape/1 like this:
Enum.any?(unquote(Macro.escape([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]])), &(unquote(char) in &1))
but then this throws another error:
** (CompileError) a.exs:10: invalid expression in guard
expanding macro: Main.is_atom_literal/1
a.exs:10: Main.test/0
This is because you're trying to call Enum.any?/2 in a guard, which is not allowed.
Fortunately, there is a workaround this: just join all the expressions with or. This can be done using Enum.reduce/3:
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
What this code does is convert is_atom_literal(c) into:
false or c in %{__struct__: Range, first: 97, last: 122} or c in %{__struct__: Range, first: 65, last: 90} or c in %{__struct__: Range, first: 48, last: 57} or c in '_-'
which is a valid guard expression as Elixir later desugars in for ranges and lists into even simpler statements (something like c >= 97 and c <= 122 or c >= 65 and c <= 90 or ...).
The code still fails since your input is 'b' while the macro expects one character. Changing 'b' to ?b works:
defmodule Main do
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
def test do
c = ?b
case c do
c when is_atom_literal(c) ->
:ok
end
end
end
IO.inspect Main.test
Output:
:ok
Related
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
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"
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.
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.
I'm trying to define two macros with the following code but it failed with ** (CompileError) iex:12: undefined function name/0.
The function parameter name cannot be unquoted in the do block of defmacro.
What is the reason of this ?
Is there any way to solve this?
(Elixir version is 1.2.5)
defmodule IEx.MyHelpers do
def default_env do
__ENV__
end
[:functions, :macros] |> Enum.each(fn name ->
defmacro unquote(name)(option \\ :all) do
import MapSet
quote do
case unquote(option) do
x when x in [:a, :all] -> __ENV__ |> Map.take([unquote(name)])
x when x in [:d, :default] -> default_env |> Map.take([unquote(name)])
x when x in [:i, :imported] ->
difference(new(Map.take(__ENV__, [unquote(name)])),
new(Map.take(default_env, [unquote(name)])))
|> MapSet.to_list
end
end
end
end)
end
You basically need to unquote twice, since the dynamic macro generation already is an implicit macro. You should be fine with adding the following line at the top of your defmacro:
name = unquote(name)