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
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!
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 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.
I see. If this is all a game, I’m happy to play along
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
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.
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.