Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write Julia macro that returns a function

First post here, thanks for reading!

Problem: I have a Vector{String} - call it A - where each element is a part of an equation, e.g. the first element of A is "x[1] - (0.8*x[1])". I would like to write a macro that takes as arguments i) a String - call it fn_name - with the name of a function, ii) the vector A, and returns a function named fn_name which looks like

function fn_name(f, x)
  f[1] = x[1] - (0.8*x[1])
  f[2] = (exp(x[4]) - 0.8*exp(x[3]))^(-1.1) - (0.99*(exp(x[4]) - 0.8*exp(x[4]))^(-1.1)*(1.0 - 0.025 + 0.30*exp(x[1])*exp(x[2])^(0.30 - 1.0)))
  f[3] = exp(x[2]) - ((1.0 - 0.025)*exp(x[2]) + exp(x[1])*exp(x[2])^0.30 - exp(x[4]))
  f[4] = x[3] - (x[4])
end 

where each rhs is one element of

A = ["x[1] - (0.8*x[1])", "(exp(x[4]) - 0.8*exp(x[3]))^(-1.1) - (0.99*(exp(x[4]) - 0.8*exp(x[4]))^(-1.1)*(1.0 - 0.025 + 0.30*exp(x[1])*exp(x[2])^(0.30 - 1.0)))", "exp(x[2]) - ((1.0 - 0.025)*exp(x[2]) + exp(x[1])*exp(x[2])^0.30 - exp(x[4]))", "x[3] - (x[4])"]

What I tried: my best attempt at solving the problem is the following

macro make_fn(fn_name, A)
    esc(quote
        function $(Symbol(fn_name))(f, x)
            for i = 1:length($(A))
                f[$i] = Meta.parse($(A)[$i])
            end
        end
    end)
end

which however doesn't work: when I run @make_fn("my_name", A) I get the error LoadError: UndefVarError: i not defined.

I find it quite hard to wrap my head around Julia metaprogramming, and while I'd be very happy to avoid using it, I think for this problem it's unavoidable.

Can you please help me understand where my mistake is?

Thanks

like image 923
albep Avatar asked Dec 17 '20 12:12

albep


People also ask

How do I write a macro for Julia?

Macros have a dedicated character in Julia's syntax: the @ (at-sign), followed by the unique name declared in a macro NAME ... end block.

How do you create a function in Julia?

Functions in Julia can be combined by composing or piping (chaining) them together. Function composition is when you combine functions together and apply the resulting composition to arguments. You use the function composition operator ( ∘ ) to compose the functions, so (f ∘ g)(args...) is the same as f(g(args...)) .

What is a macro in Julia?

Macros change existing source code or generate entirely new code. They are not some kind of more powerful function that unlocks secret abilities of Julia, they are just a way to automatically write code that you could have written out by hand anyway.

What is metaprogramming in Julia?

Metaprogramming may be defined as the programming in which we write Julia code to process and modify Julia code. With the help of Julia metaprogramming tools, one can write Julia programming code that modifies other parts of the source code file. These tools can even control when the modified code runs.


1 Answers

Macros in this case are not only avoidable, but even inapplicable, unless A is literal known at compile time.

I can provide a solution using eval and some closures:

julia> function make_fn2(A)
           Af = [@eval(x -> $(Meta.parse(expr))) for expr in A]
           function (f, x)
               for i in eachindex(A, f)
                   f[i] = Af[i](x)
               end
               return f
           end
       end
make_fn2 (generic function with 1 method)

julia> fn_name = make_fn2(A)
#46 (generic function with 1 method)

julia> fn_name(zeros(4), [1,2,3,4])
4-element Array{Float64,1}:
  0.19999999999999996
 -0.06594092302655707
 49.82984401122239
 -1.0

with the restrictions that

  1. eval will evaluate the expressions in a global scope of the module where this is defined (so it is potentially a different scope that a scope of the calling function), and
  2. the newly created function will work only if you first return to global scope (i.e. it will not work if you try to run it within a function in which you have created it).

But I'd really recommend thinking about a better input format than strings.

like image 74
phipsgabler Avatar answered Oct 02 '22 06:10

phipsgabler