如何使结构的宏生成在结构上匹配的函数方法?
How do I make a macro for a struct generate a function method matching on the struct?
请原谅标题中任何混淆的术语,但想象一下我想要一个小宏来标记我创建的结构可用于某些邪恶目的。我写这个小模块:
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
但是,尝试使用我的宏
julia> include("Usable.jl")
Main.Usable
julia> using Main.Usable
julia> @usable struct Foo
bar::String
end
结果
ERROR: UndefVarError: Foo not defined
结构显然定义得很好
julia> Foo("soup")
Foo("soup")
所以似乎比我预期的更早需要定义。我显然遗漏了什么,但我不知道是什么。
始终查看宏的输出:
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
问题是宏输出在其定义的模块内展开,这混淆了名称的含义。在这种情况下,我们希望 Foo
引用定义它的名称空间,但是:
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
其实很简单——只要转义输出即可:
macro usable(expr::Expr)
name = expr.args[2]
return esc(quote
$expr
$Usable.isusable(::Type{$name}) = true
end)
end
但请再次阅读宏文档。 esc
非常复杂,您不想盲目地将其应用到您编写的所有内容中。
另一件事(我希望做对)是拼接模块本身 -- $Usable
-- 而不是按名称引用它。否则在外面重命名模块名称可能会出问题。
在所描述的场景中,您几乎总是应该使用 Julia 强大的类型系统和多重调度机制,而不是宏。 (也许您有充分的理由这样做,但这是给其他人的信息。)
该模式是通过抽象类型简单地定义所需的行为,该抽象类型稍后由自定义 struct
.
继承
这里有一个示例,用于将 Comparable
行为添加到复合 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))
现在使用:
julia> struct MyStruct <: Comparable
f::Vector{String}
end;
julia> MyStruct(["hello"]) == MyStruct(["hello"])
true
请原谅标题中任何混淆的术语,但想象一下我想要一个小宏来标记我创建的结构可用于某些邪恶目的。我写这个小模块:
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
但是,尝试使用我的宏
julia> include("Usable.jl")
Main.Usable
julia> using Main.Usable
julia> @usable struct Foo
bar::String
end
结果
ERROR: UndefVarError: Foo not defined
结构显然定义得很好
julia> Foo("soup")
Foo("soup")
所以似乎比我预期的更早需要定义。我显然遗漏了什么,但我不知道是什么。
始终查看宏的输出:
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
问题是宏输出在其定义的模块内展开,这混淆了名称的含义。在这种情况下,我们希望 Foo
引用定义它的名称空间,但是:
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
其实很简单——只要转义输出即可:
macro usable(expr::Expr)
name = expr.args[2]
return esc(quote
$expr
$Usable.isusable(::Type{$name}) = true
end)
end
但请再次阅读宏文档。 esc
非常复杂,您不想盲目地将其应用到您编写的所有内容中。
另一件事(我希望做对)是拼接模块本身 -- $Usable
-- 而不是按名称引用它。否则在外面重命名模块名称可能会出问题。
在所描述的场景中,您几乎总是应该使用 Julia 强大的类型系统和多重调度机制,而不是宏。 (也许您有充分的理由这样做,但这是给其他人的信息。)
该模式是通过抽象类型简单地定义所需的行为,该抽象类型稍后由自定义 struct
.
这里有一个示例,用于将 Comparable
行为添加到复合 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))
现在使用:
julia> struct MyStruct <: Comparable
f::Vector{String}
end;
julia> MyStruct(["hello"]) == MyStruct(["hello"])
true