# Metaprogramming with @generated

**URL:** <https://discourse.julialang.org/t/metaprogramming-with-generated/91248>\
**Category:** General Usage\
**Tags:** metaprogramming, generated\
**Created:** [December 5, 2022, 8:07am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248 "2022-12-05T08:07:37Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![chelseas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chelseas/32/12148_2.png) [@chelseas](https://discourse.julialang.org/u/chelseas)\
**Post date:** [December 5, 2022, 8:07am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/1 "2022-12-05T08:07:37Z")

</div>

I would like to write a simple function where I give it the operator I would like to apply as a symbol and it returns a runnable function that I can actually use. e.g.

`@generated genmyfun( op , args) = :($op ( args )) `  
And I would use it like:  
`genmyfun( :+, args)([5,6])` and it would return `11`.

Essentially, I want to recreate the behavior of `eval`, but in a way that is faster. Any ideas?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [December 5, 2022, 9:03am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/2 "2022-12-05T09:03:26Z")

</div>

> [@chelseas](#):
>
> Essentially, I want to recreate the behavior of `eval`, but in a way that is faster. Any ideas?

You can’t, I think. Why does the operator have to be a symbol? If you want to make a version of a function that can be called on an iterable of arguments, you can use `Base.splat` or `Splat` in recent Julia versions:

```julia
julia> Base.splat(+)([5, 6])
11

```

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [December 5, 2022, 2:56pm UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/3 "2022-12-05T14:56:38Z")

</div>

I’m not sure `@generated` is a good tool here.

When `eval` is only used to evaluate a variable (identified by its name as a `Symbol`) and retrieve the value bound to it, it’s _sometimes_ possible to use `getproperty(module, name)` instead:

```julia
julia> args = (5, 6);
julia> op = :+;

julia> getproperty(Base, op)(args...)
11

```

  

I’m not sure how you plan to use this, but a simple benchmark against a plain `eval` could look like this:

```julia
function f1(args)
    res = 0.0
    for op in (:+, :-, :*, :/)
        res += getproperty(Base, op)(args...)
    end
    res
end

function f2(args)
    res = 0.0
    for op in (:+, :-, :*, :/)
        res += @eval $op(args...)
    end
    res
end

```

```julia
julia> using BenchmarkTools
julia> args = (5, 6);

julia> @btime f1($args)
  143.053 ns (5 allocations: 80 bytes)
40.833333333333336

julia> @btime f2($args)
  193.963 μs (161 allocations: 7.77 KiB)
40.833333333333336

```

---

<div class="post-metadata">

**Author:** ![chelseas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chelseas/32/12148_2.png) [@chelseas](https://discourse.julialang.org/u/chelseas)\
**Post date:** [December 6, 2022, 5:55am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/4 "2022-12-06T05:55:58Z")

</div>

I know of splat, it’s not what I’m looking for, but thanks anyway.

---

<div class="post-metadata">

**Author:** ![chelseas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chelseas/32/12148_2.png) [@chelseas](https://discourse.julialang.org/u/chelseas)\
**Post date:** [December 6, 2022, 5:56am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/5 "2022-12-06T05:56:38Z")

</div>

thanks @ffevotte this is great! I did not know of `get_property`. It is amazingly fast compared to eval!

---

<div class="post-metadata">

**Author:** ![chelseas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chelseas/32/12148_2.png) [@chelseas](https://discourse.julialang.org/u/chelseas)\
**Post date:** [December 6, 2022, 6:14am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/6 "2022-12-06T06:14:30Z")

</div>

I have written small tree type for another purpose but am using it here in order to test evaluation speed and these are the results:

```julia
# Small Prototype for a new tree
import Base.show
using SymEngine
abstract type Node end 
abstract type OpNode <: Node end

mutable struct VarNode <: Node 
    symbol::Symbol
    val::Float64
end
show(io::IO, a::VarNode) = print(io, "VarNode $(a.symbol)")
mutable struct ConstNode <: Node 
    val::Float64
end
show(io::IO, a::ConstNode) = print(io, "ConstNode $(a.val)")

mutable struct AffOpNode <: OpNode
    # like +, -, *
    symbol::Symbol # operator
    val::Float64
    children::Array{Node} # ASSUME only 1 or 2 children 
end
show(io::IO, a::OpNode) = print(io, "OpNode $(a.symbol) \n $(a.children)")

mutable struct PWLOpNode <: OpNode
    symbol::Symbol # operator like max or min or relu
    val::Float64
    children::Array{Node}
end

"""
A function to replace eval. Hopefully faster.
"""
function apply_fun(op, args)
    if op == :+
        return +(args...)
    elseif op == :-
        return -(args...)
    elseif op == :*
        return *(args...)
    elseif op == :max
        return max(args...)
    elseif op == :min
        return min(args...)
    elseif op == :relu
        return relu(args...)
    end
end

"""
A function to turn an Expr into a Computational Graph with Nodes.
"""
function populate_graph(e::Expr)
    @assert e.head == :call 
    # args 
    wrapped_args = [populate_graph(a) for a in e.args[2:end]]
    # return parent node
    if e.args[1] ∈ [:+, :-, :*, :/]
        return AffOpNode(e.args[1], "", 0.0, wrapped_args)
    elseif e.args[1] ∈ [:max, :min]
        return PWLOpNode(e.args[1], "", [], 0.0, wrapped_args)
    else
        warn("Whoa bro wassup here...")
    end 
end
function populate_graph(s::Symbol)
    return VarNode(s, "", 0.0)
end
function populate_graph(r::T where T <: Real)
    return ConstNode(r)
end

"""
A function to evaluate a sample. 
"""
function eval_sample(n::OpNode, sample; mode="custom")
    # evaluate arguments 
    args = [eval_sample(a, sample) for a in n.children]
    if mode == "custom"
        return apply_fun(n.symbol, args)
    elseif mode == "eval"
        return eval(Expr(:call, n.symbol, args...))
    elseif mode == "symengine"
        return Basic(Expr(:call, n.symbol, args...))
    elseif mode == "getproperty"
        return getproperty(Base, n.symbol)(args...)
    end
end 
eval_sample(n::VarNode, sample) = sample[n.symbol]
eval_sample(n::ConstNode, sample) = n.val

# testing ~~~~~~~~~~~~~~~ 

# begin with Expr and population this type
e = :(max(min(x,y),2.45))
g = populate_graph(e)

# test eval 
x_s, y_s = rand(), rand()
## first with normal functions 
t1 = @elapsed max(min(x_s, y_s), 2.45) 
## then evaluating the graph with case switch
t2 = @elapsed eval_sample(g, Dict(:x => x_s, :y => y_s); mode="custom")
## Compared to running eval in graph
t2p5 = @elapsed eval_sample(g, Dict(:x => x_s, :y => y_s); mode="eval")
## compare to running eval natively 
t3 = @elapsed eval(:(max(eval(:(min(x_s,y_s))),2.45)))
# compare to running SymEngine 
t4 = @elapsed eval_sample(g, Dict(:x => x_s, :y => y_s); mode="symengine")
# compare to getproperty 
t5 =@elapsed eval_sample(g, Dict(:x => x_s, :y => y_s); mode="getproperty")

println("t1 native = $t1, \n t2 custom = $t2, \n t2p5 eval in graph = $t2p5, \n t3 eval = $t3, \n t4 symengine = $t4, \n t5 getproperty = $t5")

```

one run:  
t1 native = 1.1577e-5,  
t2 custom = 3.3504e-5,  
t2p5 eval in graph = 0.000177618,  
t3 eval = 0.000284704,  
t4 symengine = 9.8378e-5

Another run:  
t1 native = 2.7059e-5,  
t2 custom = 7.0033e-5,  
t2p5 eval in graph = 0.000185254,  
t3 eval = 0.000314434,  
t4 symengine = 0.000200453,  
t5 getproperty = 3.063e-5

In short the speed rankings are:  
1 - native  
2 - getproperty  
3 - my custom lookup (works for a limited subset of functions)  
4 - symengine and eval in the graph are about tied  
5 - eval written out explicitly

However I have to ask @ffevotte why it takes only nanoseconds for you to run +,- etc. but microseconds for me to run max and min…do you know?  
Edit: Tried a simpler expression with just +,- and nothing seems to be running faster than microseconds for me…maybe you just have a fast computer?

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [December 6, 2022, 8:08am UTC](https://discourse.julialang.org/t/metaprogramming-with-generated/91248/7 "2022-12-06T08:08:34Z")

</div>

> [@chelseas](#):
>
> why it takes only nanoseconds for you to run +,- etc. but microseconds for me to run max and min…do you know?

I think you’re referring to the difference between `@btime` and `@elapsed`?

```julia
julia> @elapsed f1(args)
1.83e-5

julia> @btime f1($args)
  352.910 ns (5 allocations: 80 bytes)
40.833333333333336

```
