Skip to content

Commit 45db851

Browse files
author
Tomas Lycken
committed
Update according to comments by @vtjnash [av skip]
1 parent b5e423c commit 45db851

File tree

1 file changed

+38
-24
lines changed

1 file changed

+38
-24
lines changed

doc/manual/metaprogramming.rst

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ function ``foo`` as
906906

907907
julia> stagedfunction foo(x)
908908
println(x)
909-
:(x*x)
909+
return :(x*x)
910910
end
911911
foo (generic function with 1 method)
912912

@@ -950,15 +950,17 @@ is re-used as the method body.
950950

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

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

963965
.. doctest::
964966

@@ -988,16 +990,27 @@ We can, of course, abuse this to produce some interesting behavior::
988990

989991
Since the body of the staged function is non-deterministic, its behavior
990992
is undefined; the expression returned on the *first* invocation will be
991-
used for *all* subsequent invocations with the same type. When we call
992-
the staged function with ``x`` of a new type, ``rand()`` will be called
993-
again to see which method body to use for the new type. In this case, for
994-
one *type* out of ten, ``baz(x)`` will return the string ``"boo!"``.
995-
In short: don't do this.
993+
used for *all* subsequent invocations with the same type (again, with the
994+
exception covered by the disclaimer above). When we call the staged
995+
function with ``x`` of a new type, ``rand()`` will be called again to
996+
see which method body to use for the new type. In this case, for one
997+
*type* out of ten, ``baz(x)`` will return the string ``"boo!"``.
996998

997-
While these examples are perhaps not so interesting, they have hopefully
998-
helped to illustrate how staged functions work, both in the definition end
999-
and at the call site. Next, let's build some more advanced functionality
1000-
using staged functions...
999+
*Don't copy these examples!*
1000+
1001+
These examples are hopefully helpful to illustrate how staged functions
1002+
work, both in the definition end and at the call site; however, *don't
1003+
copy them*, for the following reasons:
1004+
1005+
* the `foo` function has side-effects, and it is undefined exactly when,
1006+
how often or how many times these side-effects will occur
1007+
* the `bar` function solves a problem that is better solved with multiple
1008+
dispatch - defining `bar(x) = x` and `bar(x::Integer) = x^2` will do
1009+
the same thing, but it is both simpler and faster.
1010+
* the `baz` function is pathologically insane
1011+
1012+
Instead, now that we have a better understanding for how staged functions
1013+
work, let's use them to build some more advanced functionality...
10011014

10021015
An advanced example
10031016
~~~~~~~~~~~~~~~~~~~
@@ -1013,25 +1026,26 @@ possible implementation is the following::
10131026
for i = N-1:-1:1
10141027
ind = I[i]-1 + dims[i]*ind
10151028
end
1016-
ind + 1
1029+
return ind + 1
10171030
end
10181031

10191032
The same thing can be done using recursion::
10201033

10211034
sub2ind_rec(dims::()) = 1
10221035
sub2ind_rec(dims::(),i1::Integer, I::Integer...) =
1023-
i1==1 ? sub2ind(dims,I...) : throw(BoundsError())
1036+
i1==1 ? sub2ind_rec(dims,I...) : throw(BoundsError())
10241037
sub2ind_rec(dims::(Integer,Integer...), i1::Integer) = i1
10251038
sub2ind_rec(dims::(Integer,Integer...), i1::Integer, I::Integer...) =
1026-
i1 + dims[1]*(sub2ind(tail(dims),I...)-1)
1039+
i1 + dims[1]*(sub2ind_rec(tail(dims),I...)-1)
10271040

10281041
Both these implementations, although different, do essentially the same
10291042
thing: a runtime loop over the dimensions of the array, collecting the
10301043
offset in each dimension into the final index.
10311044

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

@@ -1040,7 +1054,7 @@ that calculates the index::
10401054
for i = N-1:-1:1
10411055
ex = :(I[$i] - 1 + dims[$i]*$ex)
10421056
end
1043-
:($ex + 1)
1057+
return :($ex + 1)
10441058
end
10451059

10461060
**What code will this staged function generate?**
@@ -1055,20 +1069,20 @@ function::
10551069
function sub2ind_staged_impl{N}(dims::NTuple{N}, I...)
10561070
ex = :(I[$N] - 1)
10571071
for i = N-1:-1:1
1058-
ex = :(I[$i] - 1 + dims[$i]*ex)
1072+
ex = :(I[$i] - 1 + dims[$i]*$ex)
10591073
end
1060-
:($ex + 1)
1074+
return :($ex + 1)
10611075
end
10621076

10631077
We can now execute ``sub2ind_staged_impl`` and examine the expression it
10641078
returns::
10651079

10661080
julia> sub2ind_staged_impl((Int,Int), Int, Int)
1067-
:(((I[1] - 1) + dims[1] * ex) + 1)
1081+
:(((I[1] - 1) + dims[1] * ex) + 1)
10681082

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

0 commit comments

Comments
 (0)