How do I define a macro within a function?

I want to define a macro within a function, that uses an input argument of the function:

function runModel(; model::CoronaModel, n=10, simDuration=2)

    macro injectModel(name)
        quote
            function $(esc(name))(args...; kwargs...)
                $model.$(esc(name))($model, args... ; kwargs...)
            end
        end
    end
    @injectModel nextSickness
     ...
end

I currently use this as a workaround,

# global scope
macro injectModel(name)
    quote
        function $(esc(name))(args...; kwargs...)
            $(esc(:(model.$name)))($(esc(:model)), args... ; kwargs...)
        end
    end
end

To be clear, in your example, the macro is supposed to create a function like the following?

function nextSickness(args...; kwargs...)
    model.nextSickness(model, args; kwargs...)
end

I dare say it is never necessary to declare a macro inside a function (and certainly also never a good idea).

In this case, you can do it with a function:

function model_closure(model, symbol)
    (args...; kwargs...) -> getproperty(model, symbol)(model, args...; kwargs...)
end

You probably know you can just do the following, but I’m guessing it’s somehow inconvenient in the real context?

nextSickness(args...; kwargs...) = model.nextSickness(model, args...; kwargs...)

I saw your other post on getting OOP-like functionality in Julia. The price you pay in headaches is steep when you go against a language’s major paradigms. Turn back while you still can! :scream:

1 Like

I know I could do nextSickness(args...; kwargs...) = model.nextSickness(model, args...; kwargs...), but I dislike unnecessary repetition. With my macro, I can do just @injectModel nextSickness; It’s so elegant. One of the reasons I love metaprogramming. I am new to Julia, and have not even read the metaprogramming docs, so I thought perhaps some way existed to achieve this inner macro. To rephrase my purpose, this was more of a Julia-learning question rather than a practical problem. Also, if the metaprogramming capabilities should be high enough, any paradigm will be expressible in the language, and one would not need to change one’s natural thinking patterns. I think Julia’s architecture around weak OOP is superior for libraries, but for quick, dirty scripts, classic OOP can be empowering.

PS: Going against the flow seems like a good way to learn the subtleties of the language :smiley: My favorite hack of the day is this little gem I found on the net:

macro copycode(name, definition)
    # @def insertme some_code...
    # @insertme => would insert some_code... here
    # from http://www.stochasticlifestyle.com/type-dispatch-design-post-object-oriented-programming-julia/
  return quote
      macro $(esc(name))()
          esc($(Expr(:quote, definition)))
      end
  end
end

It’s the most beautiful iteration of the copy-paste paradigm I have seen:)) Simply graceful.

If this is a Julia learning exercise, then I think the important lesson to take away is that this cannot be done. You cannot define a macro within a function and then use it within the same function. That’s just not how the language works. Macros are expanded before the function is run. If the macro is defined inside the function, then by definition it cannot exist before the function is run. Thus what you are asking for is not possible.

Fortunately, it’s also not necessary. There’s no reason for your macro to be defined inside the function at all. If you just want:

@injectModel nextSickness

to be transformed into

nextSickness(...) = model.nextSickness(...)

then there is no reason for the macro definition to live inside the function. Just write the macro outside and use it within the function.

3 Likes

I see. If this is all a game, I’m happy to play along :smile:
You can get essentially the OOP feel in julia, it’s just a bad idea. Note that I don’t condone the following

struct MyStruct{F<:Function}
    a::Int
    some_f::F
end

function Base.getproperty(ms::MyStruct, s::Symbol)
    f = getfield(ms, s)
    f isa Function ? x->f(ms, x) : f
end

julia> s = MyStruct(1, (c, x) -> c.a + x);

julia> s.some_f(10)
11

:man_facepalming:

2 Likes

What’s the best way to structure a script that has some initial parameters (in a mathematical sense, not parametrized types) that stay constant during its run? I currently use a wrapper function that accepts the initial params, and define the functions that depend on these params as inner functions in this big wrapper function. That’s why I am trying to define macros in a function. Using the global scope directly, without a wrapper function, will cause performance problems, won’t it? Because the compiler can’t optimize on global variables (which the initial params would then be.). (Using const definitions make the REPL workflow impossible.)

PS: I did say that I am using a global macro as a workaround in my question. It works, but I have hardcoded the symbol :model in it, which felt a bit unclean at first. Now that I think about it, it doesn’t seem very problematic.

Don’t write scripts. Write functions, and pass parameters as arguments rather than using global data.

4 Likes

And also look at Parameters.jl, which was pretty much designed for this use case.

4 Likes

Common Lisp allowed you to do it, though of course it couldn’t use the local variables, just the “local syntax”. It might seem pointless, but it was very useful in fancier macros. You could have

function blah(df)
    @with_dataframe_columns df begin
        @delete_col People
        @set EyeColor .= :blue
    end
end

Where @with_dataframe_columns would define the local macros @delete_col and @set, which use df in their expansion. You can accomplish the same in Julia, but it’s a lot uglier.

2 Likes