is it possible to execute a code inside an expression? - macros

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"

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

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.

Hash function for custom type

I'm trying to define a custom equality method for a new type I've constructed. This is a MWE getting at what I'm trying to do.
mutable struct a
first_num::Int
second_num::Int
end
import Base.==
import Base.hash
function hash(obj::a, h=33141651)
return hash((obj.first_num, obj.second_num), h)
end
function ==(obj1::a, obj2::a)
if hash(obj1) == hash(obj2)
return true
else
return false
end
end
a1 = a(2,3)
a2 = a(2,3)
a1 == a2
I then get an error like ERROR: TypeError: ==: in typeassert, expected UInt64, got Int64
Is h becoming Int64?
In addition, if hashing the tuple of attributes is simply not the correct way to do this, let me know.
Edit: Actually, I ran this and I'm getting MethodError: no method matching hash(::Tuple{Int64,Int64}, ::Int64). Is h being promoted to Int64?
The problem is that your literal value for h (33141651) is an Int rather than a UInt. Thus, when you call hash with the tuple h is an Int, but the internal tuple hash function expects a UInt. I don't think you have to specify a value for h at all, and something like this should be enough:
function Base.hash(obj::a, h::UInt)
return hash((obj.first_num, obj.second_num), h)
end
Full example for completeness:
mutable struct A
first::Int
second::Int
end
function Base.hash(obj::A, h::UInt)
return hash((obj.first, obj.second), h)
end
function Base.:(==)(obj1::A, obj2::A)
return hash(obj1) == hash(obj2)
end
With the following behaviour
julia> a = A(2,3); b = A(2,3)
A(2, 3)
julia> hash(a)
0x965b43497b212144
julia> hash(b)
0x965b43497b212144
julia> a == b
true

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]

Expand range in macro

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