Julia Macro to Save the Overt form of a Function, and Define it

Viewed 61

Some of the parameters to a simulation I am writing are functions. When the output is generated, I want to put the definition of these functional parameters in the output. I have in mind a macro that somehow saves the definition as a string, and then defines it. For example, here's what I do now:

borda_score_fn(p) = exp(1/p)                                                                                                                 
global g_borda_score_fn_string = "exp(1/p)"   

And then I write g_borda_score_fn_string to my output. But this is really ugly!

What I would like to do is something like this:

@paramfn borda_score_fn(p) = exp(1/p)

And later be able to both call borda_score_fn(p), and have the form (i.e., "exp(1/p)") available for writing to my output log. (The string form might get stashed in a global dict, actually, they both could.)

I have tried many version of this, but can't get the right set of parses and calls to get it to work. Any help would be appreciated.

2 Answers

This may be a bit different than what you have in mind, but one perhaps "Julian" approach might be to have the function itself return the form string via multiple dispatch, rather than defining a whole new global variable just for that. For example, say we have a type

struct Form end

that we can use for dispatch, then we can write

borda_score_fn(p) = exp(1/p)
borda_score_fn(::Form) = "exp(1/p)"

which can then be retrieved just by calling the function with our type

julia> borda_score_fn(2)
1.6487212707001282

julia> borda_score_fn(Form())
"exp(1/p)"

That might actually be not bad on its own. But, if you want a macro to do both parts at once, then something along the lines of

macro paramfn(e)
    name = esc(e.args[1].args[1])
    str = string(e.args[2].args[2])
    f = esc(e)
    quote
        $name(::Form) = $str
        $f
    end
end

would let you write

julia> @paramfn borda_score_fn(p) = exp(1/p)
borda_score_fn (generic function with 2 methods)

julia> borda_score_fn(1)
2.718281828459045

julia> borda_score_fn(Form())
"exp(1 / p)"

For completeness, here's how you can do it in a way more similar to your original approach, but more idiomatically than with a global variable:

julia> module FormOf
           export formof, @paramfn
           
           function formof end
           macro paramfn(expr)
               name = esc(expr.args[1].args[1])
               form_str = string(expr.args[2].args[2])
               quote
                   $(esc(expr))
                   $FormOf.formof(::typeof($name)) = $form_str
                   $name
               end
           end
       end
Main.FormOf

julia> FormOf.@paramfn borda_score_fn(p) = exp(1/p)
borda_score_fn (generic function with 1 method)

julia> FormOf.formof(borda_score_fn)
"exp(1 / p)"

However, since it defines a new method of FormOf.formof, this only works in global scope:

julia> function bla()
           FormOf.@paramfn fn(p) = exp(1/p)
           fn(10) + 1
       end

ERROR: syntax: Global method definition around REPL[45]:10 needs to be placed at the top level, or use "eval".
Stacktrace:
 [1] top-level scope
   @ REPL[50]:1

@cbk's solution does not have this limitation.

Related