# Automatically generating methods with a macro

**URL:** <https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320>\
**Category:** General Usage\
**Created:** [August 17, 2024, 9:33pm UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320 "2024-08-17T21:33:50Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Yly](https://avatars.discourse-cdn.com/v4/letter/y/b9e5f3/32.png) [@Yly](https://discourse.julialang.org/u/Yly)\
**Post date:** [August 17, 2024, 9:33pm UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/1 "2024-08-17T21:33:50Z")

</div>

I was recently implementing a bunch of functions which all had multiple methods with identical structure. The methods differed only in their arguments and some preprocessing steps. EDIT (borrowed from a later post): The general form of these functions can be described like this:

- We have `n` functions `f_1`, `f_2`, …, `f_n`.
- For each function, we have `m` methods.
- Each method has the form `f_j(x_k,y_j) = (expr_k ; expr_j)`, where `x_k` are arguments that depend only on the method and `y_j` are arguments that depend only on the function, and `expr_k` is a code block that depends only on the method and `expr_j` is a code block that depends only on the function.

I would like to be able to automatically generate all the methods for all the functions, while only specifying `x_k`, `y_j`, `expr_k`, and `expr_j` once. I have a solution for this problem, but I suspect there exist much better solutions.

Here’s a toy example to illustrate the problem, with n=2 and m=2:

```julia
struct Foo
    data::Vector{Float64}
    xMax::Real
    n::Int
end

function linearFoo(xMax::Real,n::Int,a::Real,b::Real)
    x = (1:n) .* xMax # Boilerplate
    data = a .+ b .* x # This is the important line
    return Foo(data, xMax, n)
end

function linearFoo(f::Foo,a::Real,b::Real)
    xMax = f.xMax # Boilerplate
    n = f.n # Boilerplate
    x = (1:n) .* xMax # Boilerplate
    data = a .+ b .* x # This is the important line
    return Foo(data, xMax, n)
end

function quadraticFoo(xMax::Real,n::Int,a::Real,b::Real,c::Real)
    x = (1:n) .* xMax
    data = a .+ b .* x .+ c .* x.^2
    return Foo(data, xMax, n)
end

function quadraticFoo(f::Foo,a::Real,b::Real,c::Real)
    xMax = f.xMax
    n = f.n
    x = (1:n) .* xMax
    data = a .+ b .* x .+ c .* x.^2
    return Foo(data, xMax, n)
end

```

I’m a complete novice at metaprogramming, but this seemed like something I could automate with a macro. I managed to do so, but I’m not sure that my solution was remotely optimal. I’m interested to know if anyone else has dealt with similar issues and has an elegant solution. Here’s what I came up with:

```julia
# Template for the first method
methodPattern1 = quote
    function makeFoo(xMax::Real,n::Int)
         x = (1:n) .* xMax
    end
end

# Template for the second method
methodPattern2 = quote
    function makeFoo(f::Foo)
        xMax = f.xMax
        n = f.n
        x = (1:n) .* xMax
    end
end

# Helpers to extract template data
patternRef_args(x::Expr) = x.args[2].args[1].args # push! arguments here
patternRef_body(x::Expr) = x.args[2].args[2].args # push! function body here

# Helpers to extract new function data
funcRef_name(x::Expr) = x.args[1].args[1]
funcRef_args(x::Expr) = x.args[1].args[2:end]
funcRef_body(x::Expr) = x.args[2]

# Update the pattern with the new function data
function updateFuncExpr!(pattern,func)
    patternRef_args(pattern)[1] = funcRef_name(func)
    push!(patternRef_args(pattern),funcRef_args(func)...)
    push!(patternRef_body(pattern),funcRef_body(func))
end

# Put it all together
macro addTemplateMethods(funcExpr)
    patt1 = copy(methodPattern1)
    updateFuncExpr!(patt1,funcExpr)
    
    patt2 = copy(methodPattern2)
    updateFuncExpr!(patt2,funcExpr)

    return quote 
        $(esc(patt1))
        $(esc(patt2))
    end
end

```

Example usage:

```julia
@addTemplateMethods function cubicFoo(a::Real,b::Real,c::Real,d::Real)
 data = a .+ b .* x .+ c .* x.^2 .+ d .* x.^3
 return Foo(data, xMax, n)
end

cubicFoo(10,20,1,2,3,4)

```

The real thing was a bit more complicated, as I handled parametric types and keyword args, but it had the same flavor. It nicely simplifies the process of adding both new functions and new methods (e.g. if I wanted a method that took a `Foo` argument, but allowed you to overwrite the `xMax` parameter). However, the metaprogramming code that extracts parts of the Expr tree is very opaque and a bit fragile, and I wonder if there’s a better way to accomplish this.

Any suggestions for how to improve this?

---

<div class="post-metadata">

**Author:** ![jakobjpeters](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jakobjpeters/32/207797_2.png) [@jakobjpeters](https://discourse.julialang.org/u/jakobjpeters)\
**Post date:** [August 18, 2024, 4:35am UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/2 "2024-08-18T04:35:00Z")

</div>

Macros are really powerful and fun! I think this example is better solved with multiple dispatch. Is this correct? If so, adding a new case is just implementing `foo(args...)` with a new number of parameters.

```julia-repl
julia> struct Foo{T <: AbstractVector{<:Real}, S <: Real}
           data::T
           xMax::S
           n::Int
       end

julia> Foo(f::Foo, args...) = Foo(f.xMax, f.n, args...);

julia> Foo(xMax::Real, n::Int, args...) = Foo(foo((1:n) * xMax, args...), xMax, n);

julia> foo(x, a, b) = @. a + b * x;

julia> foo(x, a, b, c) = @. a + b * x + c * x ^ 2;

julia> x = Foo(2, 3, 4, 5)
Foo{StepRangeLen{Int64, Int64, Int64, Int64}, Int64}(14:10:34, 2, 3)

julia> y = Foo(x, 6, 7, 8)
Foo{Vector{Int64}, Int64}([52, 162, 336], 2, 3)

```

---

<div class="post-metadata">

**Author:** ![jakobjpeters](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jakobjpeters/32/207797_2.png) [@jakobjpeters](https://discourse.julialang.org/u/jakobjpeters)\
**Post date:** [August 18, 2024, 4:41am UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/3 "2024-08-18T04:41:32Z")

</div>

~~Also, `@evalpoly` might solve the general case (admittedly I don’t roam this territory often, so someone else please confirm)~~ ah nevermind, it doesn’t handle vectors 😃

---

<div class="post-metadata">

**Author:** ![Yly](https://avatars.discourse-cdn.com/v4/letter/y/b9e5f3/32.png) [@Yly](https://discourse.julialang.org/u/Yly)\
**Post date:** [August 18, 2024, 5:36am UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/4 "2024-08-18T05:36:17Z")

</div>

Not quite. I think your solution requires that the different methods have unique signatures, which is not something that holds in general (although coincidentally it did in my toy example). E.g. I might want a function like

```julia
function gaussianFoo(f::Foo,w::Real,h::Real)
    xMax = f.xMax  
    n = f.n  
    x = (1:n) .* xMax
    data = a .* exp.(-x.^2 ./ b^2) 
    return Foo(data, xMax, n)
end

```

I guess the general problem I want to solve looks like this, ignoring complexities like parametric types and keyword args:

- We have `n` functions `f_1`, `f_2`, …, `f_n`.
- For each function, we have `m` methods.
- Each method has the form `f_j(x_k,y_j) = (expr_k ; expr_j)`, where `x_k` are arguments that depend only on the method and `y_j` are arguments that depend only on the function, and `expr_k` is a code block that depends only on the method and `expr_j` is a code block that depends only on the function.
- I would like to be able to automatically generate all the methods for all the functions, while only specifying `x_k`, `y_j`, `expr_k`, and `expr_j` once.

Another simple example to keep in mind is a function which can accept either a vector or a tuple as an argument. The methods in either case are almost identical, but you might want to do some processing in one or the other case, e.g. to make sure that the vector has the appropriate length or to do type conversions.

---

<div class="post-metadata">

**Author:** ![jakobjpeters](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jakobjpeters/32/207797_2.png) [@jakobjpeters](https://discourse.julialang.org/u/jakobjpeters)\
**Post date:** [August 18, 2024, 5:58am UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/5 "2024-08-18T05:58:58Z")

</div>

How about parameterizing the `data` function? That way, the boilerplate is still abstract away and you can select which one to call.

EDIT: this still leaves methods for `polynomial` and `guassian` to be generated, but it removes a lot of external complexity

```julia-repl
julia> Foo(f::Function, xMax::Real, n::Int, args...) = Foo(f((1:n) * xMax, args...), xMax, n);

julia> Foo(gaussian, 2, 3, 4, 5)

```

---

<div class="post-metadata">

**Author:** ![jakobjpeters](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jakobjpeters/32/207797_2.png) [@jakobjpeters](https://discourse.julialang.org/u/jakobjpeters)\
**Post date:** [August 18, 2024, 6:09am UTC](https://discourse.julialang.org/t/automatically-generating-methods-with-a-macro/118320/6 "2024-08-18T06:09:45Z")

</div>

```julia-repl
julia> @generated function polynomial(x, args...)
           # return `Expr` based on number of parameters
       end

```
