Skip to content

Commit

Permalink
Change terminology according to JuliaLang#10884 [av skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
Tomas Lycken committed Apr 20, 2015
1 parent 45db851 commit c4719d2
Showing 1 changed file with 61 additions and 55 deletions.
116 changes: 61 additions & 55 deletions doc/manual/metaprogramming.rst
Original file line number Diff line number Diff line change
Expand Up @@ -872,49 +872,52 @@ entirely in Julia. You can read their source and see precisely what they
do — and all they do is construct expression objects to be inserted into
your program's syntax tree.

Staged functions
Generated functions
----------------

This comment has been minimized.

Copy link
@mbauman

mbauman Apr 20, 2015

I don't know RST very well, but I do know it's pretty picky at times. Does it need this underline to be the same length as the header?


*Staged functions* play a similar role as macros, but at a later stage
between parsing and run-time. Staged functions give the capability to
generate specialized code depending on the types of their arguments.
While macros work with expressions at parsing-time and cannot access the
types of their inputs, a staged function gets expanded at a time when
the types of the arguments are known, but the function is not yet compiled.
A very special macro is ``@generated``, which allows you to define so-called
*generated functions*. These have the capability to generate specialized
code depending on the types of their arguments with more flexibility and/or
less code than what can be achieved with multiple dispatch. While macros

This comment has been minimized.

Copy link
@mbauman

mbauman Apr 20, 2015

👍 for the reference to multiple dispatch. The more I work with staged generated functions the more I think of them as a way to programmatically populate the method table.

work with expressions at parsing-time and cannot access the types of their
inputs, a generated function gets expanded at a time when the types of
the arguments are known, but the function is not yet compiled.

Depending on the types of the arguments, a staged function returns a quoted
expression which then forms the function body of the specialized function.
Thus, staged functions provide a flexible framework to move work from
run-time to compile-time.
Depending on the types of the arguments, a generated function returns a
quoted expression which then forms the method body of the specialized
method. Thus, generated functions provide a flexible framework to move
work from run-time to compile-time.

When defining staged functions, there are three main differences to
When defining generated functions, there are three main differences to
ordinary functions:

1. You use the keyword ``stagedfunction`` instead of ``function``
1. You annotate the function declaration with the ``@generated`` macro.
This adds some information to the AST that lets the compiler know that
this is a generated function.

2. In the body of the ``stagedfunction`` you only have access to the
2. In the body of the generated function you only have access to the
*types* of the arguments, not their values.

3. Instead of calculating something or performing some action, you return
from the staged function a *quoted expression* which, when evaluated,
does what you want.
from a *quoted expression* which, when evaluated, does what you want.

It's easiest to illustrate this with an example. We can declare a staged
It's easiest to illustrate this with an example. We can declare a generated
function ``foo`` as

.. doctest::

julia> stagedfunction foo(x)
julia> @generate foo(x)

This comment has been minimized.

Copy link
@mbauman

mbauman Apr 20, 2015

@generated function foo(x)

println(x)
return :(x*x)
end
foo (generic function with 1 method)

Note that the body returns a quoted expression, namely ``x*x``.
Note that the body returns a quoted expression, namely ``:(x*x)``, rather
than just the value of ``x*x``.

From the callers perspective, they are very similar to regular functions;
in fact, you don't have to know if you're calling a ``function`` or a
``stagedfunction`` - the syntax and result of the call is just the same.
in fact, you don't have to know if you're calling a regular or generated
function or a - the syntax and result of the call is just the same.
Let's see how ``foo`` behaves:

.. doctest::
Expand All @@ -929,10 +932,10 @@ Let's see how ``foo`` behaves:
julia> y
"barbar"

So, we see that in the body of the ``stagedfunction``, ``x`` is the
*type* of the passed argument, and the value returned by the ``stagedfunction``,
is the result of evaluating the quoted expression we returned from the
definition, now with the *value* of ``x``.
So, we see that in the body of the generated function, ``x`` is the
*type* of the passed argument, and the value returned by the generated
function, is the result of evaluating the quoted expression we returned
from the definition, now with the *value* of ``x``.

What happens if we evaluate ``foo`` again with a type that we have already
used?
Expand All @@ -942,29 +945,29 @@ used?
julia> foo(4)
16

Note that there is no printout of ``Int64``. The body of the ``stagedfunction``
is only executed *once* (not entirely true, see note below) when the method
for that specific set of argument types is compiled. After that, the
expression returned from the ``stagedfunction`` on the first invocation
Note that there is no printout of ``Int64``. The body of the generated
function is only executed *once* (not entirely true, see note below) when
the method for that specific set of argument types is compiled. After that,
the expression returned from the generated function on the first invocation
is re-used as the method body.

The reason for the disclaimer above is that the number of times a staged
function is staged is really an implementation detail; it *might* be only
The reason for the disclaimer above is that the number of times a generated
function is generated is really an implementation detail; it *might* be only
once, but it *might* also be more often. As a consequence, you should
*never* write a staged function with side effects - when, and how often,
*never* write a generated function with side effects - when, and how often,
the side effects occur is undefined. (This is true for macros too - and just
like for macros, the use of `eval` in a staged function is a sign that
like for macros, the use of `eval` in a generated function is a sign that
you're doing something the wrong way.)

The example staged function ``foo`` above did not do anything a normal
The example generated function ``foo`` above did not do anything a normal
function ``foo(x)=x*x`` could not do, except printing the the type on the
first invocation and incurring a higher compile-time cost. However, the
power of a staged function lies in its ability to compute different quoted
first invocation (and incurring a higher compile-time cost). However, the
power of a generated function lies in its ability to compute different quoted
expression depending on the types passed to it:

.. doctest::

julia> stagedfunction bar(x)
julia> @generated function bar(x)
if x <: Integer
return :(x^2)
else
Expand All @@ -978,27 +981,30 @@ expression depending on the types passed to it:
julia> bar("baz")
"baz"

(although of course this contrived example is easily implemented using
multiple dispatch...)

We can, of course, abuse this to produce some interesting behavior::

julia> stagedfunction baz(x)
julia> @generated function baz(x)
if rand() < .9
return :(x^2)
else
return :("boo!")
end
end

Since the body of the staged function is non-deterministic, its behavior
Since the body of the generated function is non-deterministic, its behavior
is undefined; the expression returned on the *first* invocation will be
used for *all* subsequent invocations with the same type (again, with the
exception covered by the disclaimer above). When we call the staged
exception covered by the disclaimer above). When we call the generated
function with ``x`` of a new type, ``rand()`` will be called again to
see which method body to use for the new type. In this case, for one
*type* out of ten, ``baz(x)`` will return the string ``"boo!"``.

*Don't copy these examples!*

These examples are hopefully helpful to illustrate how staged functions
These examples are hopefully helpful to illustrate how generated functions
work, both in the definition end and at the call site; however, *don't
copy them*, for the following reasons:

Expand All @@ -1009,7 +1015,7 @@ copy them*, for the following reasons:
the same thing, but it is both simpler and faster.
* the `baz` function is pathologically insane

Instead, now that we have a better understanding for how staged functions
Instead, now that we have a better understanding for how generated functions
work, let's use them to build some more advanced functionality...

An advanced example
Expand Down Expand Up @@ -1043,46 +1049,46 @@ thing: a runtime loop over the dimensions of the array, collecting the
offset in each dimension into the final index.

However, all the information we need for the loop is embedded in the type
information of the arguments. Thus, we can utilize staged functions to
move the iteration to compile-time; in compiler parlance, we use staged
information of the arguments. Thus, we can utilize generated functions to
move the iteration to compile-time; in compiler parlance, we use generated
functions to manually unroll the loop. The body becomes almost identical,
but instead of calculating the linear index, we build up an *expression*
that calculates the index::

stagedfunction sub2ind_staged{N}(dims::NTuple{N}, I::Integer...)
@generated function sub2ind_gen{N}(dims::NTuple{N}, I::Integer...)
ex = :(I[$N] - 1)
for i = N-1:-1:1
ex = :(I[$i] - 1 + dims[$i]*$ex)
end
return :($ex + 1)
end

**What code will this staged function generate?**
**What code will this generate?**

An easy way to find out, is to extract the body into another (regular)
function::

stagedfunction sub2ind_staged{N}(dims::NTuple{N}, I::Integer...)
sub2ind_staged_impl(dims, I...)
@generated function sub2ind_gen{N}(dims::NTuple{N}, I::Integer...)
sub2ind_gen_impl(dims, I...)
end

function sub2ind_staged_impl{N}(dims::NTuple{N}, I...)
function sub2ind_gen_impl{N}(dims::NTuple{N}, I...)
ex = :(I[$N] - 1)
for i = N-1:-1:1
ex = :(I[$i] - 1 + dims[$i]*$ex)
end
return :($ex + 1)
end

We can now execute ``sub2ind_staged_impl`` and examine the expression it
We can now execute ``sub2ind_gen_impl`` and examine the expression it
returns::

julia> sub2ind_staged_impl((Int,Int), Int, Int)
julia> sub2ind_gen_impl((Int,Int), Int, Int)
:(((I[1] - 1) + dims[1] * ex) + 1)

So, the method body that will be used here doesn't include a loop at all
- just indexing into the two tuples, multiplication and addition/subtraction.
All the looping is performed compile-time, and we avoid looping during execution
entirely. Thus, we only loop *once per type*, in this case once per ``N``
(except in edge cases where the function is staged more than once - see
disclaimer above).
All the looping is performed compile-time, and we avoid looping during
execution entirely. Thus, we only loop *once per type*, in this case once
per ``N`` (except in edge cases where the function is generated more than
once - see disclaimer above).

0 comments on commit c4719d2

Please sign in to comment.