如何使结构的宏生成在结构上匹配的函数方法?

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 行为添加到复合 structs。

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