From a1b588c60b58f2f97855e505f86f8a7b5d580025 Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Sun, 29 Mar 2015 21:53:30 +0200 Subject: [PATCH 1/7] Document stagedfunctions [av skip] This is a rebase/squash of the following commits: 8ffd923 Doc: stagedfunctions. Basic functionality and simple example 471befa Update stagedfunction doc according to comments cd19d47 Start on advanced example e02d21f Correct syntax typos 293ec59 Correct typo and improve grammar c42321b Complete advanced example --- doc/manual/metaprogramming.rst | 191 +++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index 0c2eb7ae28039..dc6bda5a1ffff 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -872,3 +872,194 @@ 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 +---------------- + +*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. + +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. + +When defining staged functions, there are three main differences to +ordinary functions: + +1. You use the keyword ``stagedfunction`` instead of ``function`` + +2. In the body of the ``stagedfunction`` 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. + +It's easiest to illustrate this with an example. We can declare a staged +function ``foo`` as + +.. doctest:: + + julia> stagedfunction foo(x) + println(x) + :(x*x) + end + foo (generic function with 1 method) + +Note that the body returns a quoted expression, namely ``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. +Let's see how ``foo`` behaves: + +.. doctest:: + + julia> x = foo(2); # note: not printing the result + Int64 # this is the println() statement in the body + julia> x # now we print x + 4 + + julia> y = foo("bar"); + ASCIIString + 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``. + +What happens if we evaluate ``foo`` again with a type that we have already +used? + +.. doctest:: + + julia> foo(4) + 16 + +Note that there is no printout of ``Int64``. The body of the ``stagedfunction`` +is only executed *once*, when the method for that specific set of argument +types is compiled. After that, the expression returned from the +``stagedfunction`` on the first invocation is re-used as the method body. + +The example staged 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. However, the power of a staged function lies in its +ability to compute different quoted expression depending on the types +passed to it: + +.. doctest:: + + julia> stagedfunction bar(x) + if x <: Integer + return :(x^2) + else + return :(x) + end + end + bar (generic function with 1 method) + + julia> bar(4) + 16 + julia> bar("baz") + "baz" + +We can, of course, abuse this to produce some interesting behavior:: + + julia> stagedfunction 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 +is undefined; the expression returned on the *first* invocation will be +used for *all* subsequent invocations with the same type. When we call +the staged 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!"``. +In short: don't do this. + +While these examples are perhaps not so interesting, they have hopefully +helped to illustrate how staged functions work, both in the definition end +and at the call site. Next, let's build some more advanced functionality +using staged functions... + +An advanced example +~~~~~~~~~~~~~~~~~~~ + +Julia's base library has a function ``sub2ind`` function to calculate a +linear index into an n-dimensional array, based on a set of n multilinear +indices - in other words, to calculate the index ``i`` that can be used to +index into an array ``A`` using ``A[i]``, instead of ``A[x,y,z,...]``. One +possible implementation is the following:: + + function sub2ind_loop(dims::NTuple{N}, I::Integer...) + ind = I[N] - 1 + for i = N-1:-1:1 + ind = I[i]-1 + dims[i]*ind + end + ind + 1 + end + +The same thing can be done using recursion:: + + sub2ind_rec(dims::()) = 1 + sub2ind_rec(dims::(),i1::Integer, I::Integer...) = + i1==1 ? sub2ind(dims,I...) : throw(BoundsError()) + sub2ind_rec(dims::(Integer,Integer...), i1::Integer) = i1 + sub2ind_rec(dims::(Integer,Integer...), i1::Integer, I::Integer...) = + i1 + dims[1]*(sub2ind(tail(dims),I...)-1) + +Both these implementations, although different, do essentially the same +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. 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...) + ex = :(I[$N] - 1) + for i = N-1:-1:1 + ex = :(I[$i] - 1 + dims[$i]*$ex) + end + :($ex + 1) + end + +**What code will this staged function 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...) + end + + function sub2ind_staged_impl(dims, I...) + ex = :(I[$N] - 1) + for i = N-1:-1:1 + ex = :(I[$i] - 1 + dims[$i]*$ex) + end + :($ex + 1) + end + +We can now execute ``sub2ind_staged_impl`` and examine the expression it +returns:: + + julia> sub2ind_staged_impl((5,2), 3, 2) + :(((I[1] - 1) + dims[1] * (I[2] - 1)) + 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, which means we only loop +*once per type*, in this case once per ``N``. From b5e423cbbf2ca7f109cba5bef90d3e7ce7e9ec5d Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Tue, 14 Apr 2015 08:48:47 +0200 Subject: [PATCH 2/7] Update according to comments [av skip] * Make `sub2ind_staged_impl` define `N` correctly * Add a note that staging *might* occur more than once. --- doc/manual/metaprogramming.rst | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index dc6bda5a1ffff..a760de9d335de 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -943,9 +943,16 @@ used? 16 Note that there is no printout of ``Int64``. The body of the ``stagedfunction`` -is only executed *once*, when the method for that specific set of argument -types is compiled. After that, the expression returned from the -``stagedfunction`` on the first invocation is re-used as the method body. +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 +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 +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, +the side effects occur is undefined. The example staged function ``foo`` above did not do anything a normal function ``foo(x)=x*x`` could not do, except printing the the type on the @@ -1045,10 +1052,10 @@ function:: sub2ind_staged_impl(dims, I...) end - function sub2ind_staged_impl(dims, I...) + function sub2ind_staged_impl{N}(dims::NTuple{N}, I...) ex = :(I[$N] - 1) for i = N-1:-1:1 - ex = :(I[$i] - 1 + dims[$i]*$ex) + ex = :(I[$i] - 1 + dims[$i]*ex) end :($ex + 1) end @@ -1056,10 +1063,12 @@ function:: We can now execute ``sub2ind_staged_impl`` and examine the expression it returns:: - julia> sub2ind_staged_impl((5,2), 3, 2) - :(((I[1] - 1) + dims[1] * (I[2] - 1)) + 1) + julia> sub2ind_staged_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, which means we only loop -*once per type*, in this case once per ``N``. +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). From 45db85141f7628ba431c2f9643b85e7c14fe448e Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Fri, 17 Apr 2015 10:30:28 +0200 Subject: [PATCH 3/7] Update according to comments by @vtjnash [av skip] --- doc/manual/metaprogramming.rst | 62 +++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index a760de9d335de..8c4bdd63db93d 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -906,7 +906,7 @@ function ``foo`` as julia> stagedfunction foo(x) println(x) - :(x*x) + return :(x*x) end foo (generic function with 1 method) @@ -950,15 +950,17 @@ 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 -once, but it *might* also be more often. As a consequence, you should +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, -the side effects occur is undefined. +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 +you're doing something the wrong way.) The example staged 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. However, the power of a staged function lies in its -ability to compute different quoted expression depending on the types -passed to it: +first invocation and incurring a higher compile-time cost. However, the +power of a staged function lies in its ability to compute different quoted +expression depending on the types passed to it: .. doctest:: @@ -988,16 +990,27 @@ We can, of course, abuse this to produce some interesting behavior:: Since the body of the staged 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. When we call -the staged 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!"``. -In short: don't do this. +used for *all* subsequent invocations with the same type (again, with the +exception covered by the disclaimer above). When we call the staged +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!"``. -While these examples are perhaps not so interesting, they have hopefully -helped to illustrate how staged functions work, both in the definition end -and at the call site. Next, let's build some more advanced functionality -using staged functions... +*Don't copy these examples!* + +These examples are hopefully helpful to illustrate how staged functions +work, both in the definition end and at the call site; however, *don't +copy them*, for the following reasons: + +* the `foo` function has side-effects, and it is undefined exactly when, + how often or how many times these side-effects will occur +* the `bar` function solves a problem that is better solved with multiple + dispatch - defining `bar(x) = x` and `bar(x::Integer) = x^2` will do + 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 +work, let's use them to build some more advanced functionality... An advanced example ~~~~~~~~~~~~~~~~~~~ @@ -1013,17 +1026,17 @@ possible implementation is the following:: for i = N-1:-1:1 ind = I[i]-1 + dims[i]*ind end - ind + 1 + return ind + 1 end The same thing can be done using recursion:: sub2ind_rec(dims::()) = 1 sub2ind_rec(dims::(),i1::Integer, I::Integer...) = - i1==1 ? sub2ind(dims,I...) : throw(BoundsError()) + i1==1 ? sub2ind_rec(dims,I...) : throw(BoundsError()) sub2ind_rec(dims::(Integer,Integer...), i1::Integer) = i1 sub2ind_rec(dims::(Integer,Integer...), i1::Integer, I::Integer...) = - i1 + dims[1]*(sub2ind(tail(dims),I...)-1) + i1 + dims[1]*(sub2ind_rec(tail(dims),I...)-1) Both these implementations, although different, do essentially the same thing: a runtime loop over the dimensions of the array, collecting the @@ -1031,7 +1044,8 @@ 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. The body becomes almost identical, +move the iteration to compile-time; in compiler parlance, we use staged +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:: @@ -1040,7 +1054,7 @@ that calculates the index:: for i = N-1:-1:1 ex = :(I[$i] - 1 + dims[$i]*$ex) end - :($ex + 1) + return :($ex + 1) end **What code will this staged function generate?** @@ -1055,20 +1069,20 @@ function:: function sub2ind_staged_impl{N}(dims::NTuple{N}, I...) ex = :(I[$N] - 1) for i = N-1:-1:1 - ex = :(I[$i] - 1 + dims[$i]*ex) + ex = :(I[$i] - 1 + dims[$i]*$ex) end - :($ex + 1) + return :($ex + 1) end We can now execute ``sub2ind_staged_impl`` and examine the expression it returns:: julia> sub2ind_staged_impl((Int,Int), Int, Int) - :(((I[1] - 1) + dims[1] * ex) + 1) + :(((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`` +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). From c4719d2ca63ffaee9bf8a6c00766a40e31c2b434 Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Mon, 20 Apr 2015 22:22:58 +0200 Subject: [PATCH 4/7] Change terminology according to #10884 [av skip] --- doc/manual/metaprogramming.rst | 116 +++++++++++++++++---------------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index 8c4bdd63db93d..a43aaf2c93611 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -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 ---------------- -*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 +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) 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:: @@ -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? @@ -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 @@ -978,9 +981,12 @@ 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 @@ -988,17 +994,17 @@ We can, of course, abuse this to produce some interesting behavior:: 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: @@ -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 @@ -1043,13 +1049,13 @@ 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) @@ -1057,16 +1063,16 @@ that calculates the index:: 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) @@ -1074,15 +1080,15 @@ function:: 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). From bd4770363f5cfe2c1d9abff373cb8d1092628bc9 Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Mon, 20 Apr 2015 22:46:57 +0200 Subject: [PATCH 5/7] Fix RST formatting + typos [av skip] --- doc/manual/metaprogramming.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index a43aaf2c93611..19f424293ecea 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -873,7 +873,7 @@ do — and all they do is construct expression objects to be inserted into your program's syntax tree. Generated functions ----------------- +------------------- A very special macro is ``@generated``, which allows you to define so-called *generated functions*. These have the capability to generate specialized @@ -899,14 +899,14 @@ ordinary functions: *types* of the arguments, not their values. 3. Instead of calculating something or performing some action, you return - from a *quoted expression* which, when evaluated, does what you want. + a *quoted expression* which, when evaluated, does what you want. It's easiest to illustrate this with an example. We can declare a generated function ``foo`` as .. doctest:: - julia> @generate foo(x) + julia> @generated function foo(x) println(x) return :(x*x) end From 59a890546ab6c2ccb91b647384c657f4b6447349 Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Mon, 20 Apr 2015 22:48:44 +0200 Subject: [PATCH 6/7] Update example with new tuple syntax [av skip] --- doc/manual/metaprogramming.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index 19f424293ecea..6c5df2f44ece5 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -1037,11 +1037,11 @@ possible implementation is the following:: The same thing can be done using recursion:: - sub2ind_rec(dims::()) = 1 - sub2ind_rec(dims::(),i1::Integer, I::Integer...) = + sub2ind_rec(dims::Tuple{}) = 1 + sub2ind_rec(dims::Tuple{},i1::Integer, I::Integer...) = i1==1 ? sub2ind_rec(dims,I...) : throw(BoundsError()) - sub2ind_rec(dims::(Integer,Integer...), i1::Integer) = i1 - sub2ind_rec(dims::(Integer,Integer...), i1::Integer, I::Integer...) = + sub2ind_rec(dims::Tuple{Integer,Vararg{Integer}}, i1::Integer) = i1 + sub2ind_rec(dims::Tuple{Integer,Vararg{Integer}}, i1::Integer, I::Integer...) = i1 + dims[1]*(sub2ind_rec(tail(dims),I...)-1) Both these implementations, although different, do essentially the same From c96d476e9b5c97f4abd932bef789b483c7c586fe Mon Sep 17 00:00:00 2001 From: Tomas Lycken Date: Tue, 21 Apr 2015 17:14:46 +0200 Subject: [PATCH 7/7] Re-word a passage on methods [av skip] According to comment by @mshauer - I have attempted to adjust this passage to the Julia terminology, hoping that I didn't make it difficult to read in the process :) --- doc/manual/metaprogramming.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/manual/metaprogramming.rst b/doc/manual/metaprogramming.rst index 6c5df2f44ece5..9b59a2c94e348 100644 --- a/doc/manual/metaprogramming.rst +++ b/doc/manual/metaprogramming.rst @@ -883,10 +883,13 @@ 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 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. +Instead of performing some calculation or action, a generated function +declaration returns a quoted expression which then forms the body for the +method corresponding to the types of the arguments. When called, the body +expression is compiled (or fetched from a cache, on subsequent calls) and +only the returned expression - not the code that generated it - is evaluated. +Thus, generated functions provide a flexible framework to move work from +run-time to compile-time. When defining generated functions, there are three main differences to ordinary functions: