Reputation: 392
Pardon any confused terminology in the title, but imagine I want to have a little macro to mark structs I create as usable for some nefarious purpose. I write this little module:
module Usable
export @usable, isusable
isusable(::Type{T}) where T = false
macro usable(expr::Expr)
name = expr.args[2]
return quote
$expr
Usable.isusable(::Type{$name}) = true # This in't working
end
end
end
However, trying to use my macro
julia> include("Usable.jl")
Main.Usable
julia> using Main.Usable
julia> @usable struct Foo
bar::String
end
results in
ERROR: UndefVarError: Foo not defined
The struct was evidently defined just fine
julia> Foo("soup")
Foo("soup")
so it would seem the definition is required earlier than I expect. I am obviously missing something, but I can't figure out what.
Upvotes: 2
Views: 402
Reputation: 42214
In the described scenario almost always you should use Julia's powerful type system and multiple dispatch mechanism not a macro. (Perhaps you have a good reason to do so but this is information for others.)
The pattern is to simply define the desired behavior through an abstract type that is later inherited by a custom struct
.
One example is here for adding a Comparable
behavior to composite struct
s.
abstract type Comparable end
import Base.==
==(a::T, b::T) where T <: Comparable =
getfield.(Ref(a),fieldnames(T)) == getfield.(Ref(b),fieldnames(T))
And now using:
julia> struct MyStruct <: Comparable
f::Vector{String}
end;
julia> MyStruct(["hello"]) == MyStruct(["hello"])
true
Upvotes: 2
Reputation: 20950
Always have a look at the output of your macros:
julia> @macroexpand @usable struct Foo
bar::String
end
quote
#= REPL[1]:11 =#
struct Foo
#= REPL[4]:2 =#
bar::Main.Usable.String
end
#= REPL[1]:13 =#
(Main.Usable.Usable).isusable(::Main.Usable.Type{Main.Usable.Foo}) = begin
#= REPL[1]:13 =#
true
end
end
The problem is that the macro output is expanded within the module it has been defined, which messes up what the names mean. In this case, we want Foo
to refer to the namespace where it was defined, though:
quote
#= REPL[1]:11 =#
struct Foo
#= REPL[10]:2 =#
bar::String
end
#= REPL[1]:13 =#
Usable.isusable(::Type{Foo}) = begin
#= REPL[1]:13 =#
true
end
end
It's actually simple to get that -- just escape the output:
macro usable(expr::Expr)
name = expr.args[2]
return esc(quote
$expr
$Usable.isusable(::Type{$name}) = true
end)
end
But do read the macro documentation again. esc
is quite intricate, and you don't want to blindly apply it to everything you write.
The other thing (and I hope to get this right) is to splice in the module itself -- $Usable
-- instead of referring to it by name. Otherwise you might have a problem if you rename the module name outside.
Upvotes: 5