Defining traits on functions using macros

Hello and welcome to discourse!

As mentioned in the previous answer, for this particular use case, you should probably try to re-use existing features: if you can avoid having to write and maintain such a macro yourself, it will probably be better in the long run.

That being said, for the sake of learning, there are few issues with your macro. A macro should be used to take as input some code that you write (i.e. syntax), and output new code that you don’t want to bother writing (new syntax). Here your macro does not output anything: it only has side effects. Using eval in a macro usually indicates that something should be fixed.

Here is a (simplified) version of what you were trying to achieve, but rewritten in such a way that it does not have any side effect. Instead, it generates some code that will itself have side effects (e.g define new methods).

# Let's put the macro in a new module, so that we can check that there
# are no issues related to module boundaries.
module Feature
export @feature

using MacroTools

macro feature(defun)
    # Use pattern matching features from MacroTools to get the
    # function name
    @capture defun function funname_(args__)
        body_
    end

    quote
        # start with outputting the method definition unchanged
        Base.@__doc__ $defun

        # generate and output some code that will conditionally
        # define new methods
        if hasmethod($funname, Tuple{Int, Float64})
            @doc "docstring for featuretype"
            featuretype(::typeof($funname)) = 1
        end
    end |> esc # escape everything: since this is destined to be
    #            used at top level, we should not have hygiene
    #            problems here
end

end # module

This macro in itself has no side effect. We can analyze what it does by checking the code it generates for a given input. Just to be clear, instead of conditionally generating method definitions, this macro generates code that will conditionally define the required methods:

julia> using .Feature
julia> @macroexpand @feature function posgx(roadway::Int, veh::Float64)
           "OK"
       end
# I cleaned up the output a bit
quote
    function posgx(roadway::Int, veh::Float64)
        "OK"
    end
    if hasmethod(posgx, Tuple{Int, Float64})
        begin
            featuretype(::typeof(posgx)) = begin
                    1
            end
        end
    end
end

Since we’re happy with the generated code, we can try it in real conditions:

julia> "docstring for posgx"
       @feature function posgx(roadway::Int, veh::Float64)
           "OK"
       end
featuretype

# The given method has been defined
julia> posgx(42, 3.)
"OK"

# The trait helper method has also been defined
julia> featuretype(posgx)
1

# And is documented
help?> featuretype
search: featuretype

  docstring for featuretype
6 Likes