Generated function with multiple return values

metaprogramming

#1

How can I create a generated function, that returns two arguments, see the following example:

ex = [:(x+y), :(x-y)]
@generated function f(x,y)
   ex[1]
end
f(2,3) # returns 5

That works fine. And how about returning two arguments (similar to the following code)?

@generated function f(x,y)
ex[1],ex[2]
end

#2

The generator (the body of the generated function) must always return exactly one expression. That is to say, it must return exactly the entire body of the function you want to generate. That’s why your second example fails: you’re returning two expressions, not a single expression.

What you need to do instead is to think about what the function you’re trying to generate would look like. It would probably look something like this:

julia> function f_generated(x, y)
         ((x + y), (x - y))
       end

Your generator needs to return the body of that function, or, in other words, it needs to return:

julia> :((x + y), (x - y))

Now you need to figure out how to construct :((x + y), (x - y)) out of your two expressions in ex. You can do that by splicing each expression into a new tuple expression:

julia> :($(ex[1]), $(ex[2]))
:((x + y, x - y))

So that’s exactly what your generator should return:

julia> @generated function f(x, y)
           :($(ex[1]), $(ex[2]))
       end
f (generic function with 1 method)

julia> f(1, 2)
(3, -1)

#3

Thanks for the thourough and clear answer! :slight_smile:

One small further question as I do not manage to get that working with expressions:
How would the return code line :($(ex[1]), $(ex[2])) look like assuming the array ex has many entries and I want to use something like the splatting syntax?


#4

I find that cases like this are easier to handle by constructing the expression directly. You can construct any Julia expression by directly building an Expr(), and you can find out how a particular expression is built using dump(). For example:

julia> dump(:(a, b))
Expr
  head: Symbol tuple
  args: Array{Any}((2,))
    1: Symbol a
    2: Symbol b

We can infer from that dump() output that we can construct a tuple expression like so:

julia> Expr(:tuple, :a, :b)
:((a, b))

Or to construct a tuple expression from arbitrarily many other expressions, we can do:

julia> Expr(:tuple, ex...)
:((x + y, x - y))

By the way, I should mention that you actually don’t need a @generated function in the case you posted originally. A @generated function is necessary when you want the generated code to depend on the types of the input arguments, but your code doesn’t actually depend on the types of x or y.

Instead, you can just use @eval to construct the function right away:

julia> @eval function f(x, y)
         ($(ex[1]), $(ex[2]))
       end
f (generic function with 1 method)

julia> f(1, 2)
(3, -1)

or, using the splatting syntax:

julia> @eval function f(x, y)
         $(Expr(:tuple, ex...))
       end
f (generic function with 1 method)

julia> f(1, 2)
(3, -1)