Skip to content

Commit 4dcda90

Browse files
committed
add docs for method replacement / world age
1 parent 8ca5a58 commit 4dcda90

File tree

2 files changed

+118
-1
lines changed

2 files changed

+118
-1
lines changed

doc/devdocs/ast.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ These symbols appear in the ``head`` field of ``Expr``\s in lowered form.
187187
Method
188188
~~~~~~
189189

190-
A unique'd container describing the shared metadata for a single (unspecialized) method.
190+
A unique'd container describing the shared metadata for a single method.
191191

192192
``name``, ``module``, ``file``, ``line``, ``sig`` - Metadata to uniquely identify the method
193193
for the computer and the human
@@ -205,6 +205,8 @@ A unique'd container describing the shared metadata for a single (unspecialized)
205205

206206
``nargs``, ``isva``, ``called``, ``isstaged`` - Descriptive bit-fields for the source code of this Method.
207207

208+
``min-age`` / ``max-age`` - The range of world ages for which this method is visible.
209+
208210

209211
MethodInstance
210212
~~~~~~~~~~~~~~
@@ -231,6 +233,8 @@ See especially :ref:`devdocs-locks` for important details on how to modify these
231233
may be put here (if ``jlcall_api == 2``), or it could be set to `nothing`
232234
to just indicate ``rettype`` is inferred
233235

236+
``min-age`` / ``max-age`` - The range of world ages for which this method instance is valid.
237+
234238
``ftpr`` - The generic jlcall entry point
235239

236240
``jlcall_api`` - The ABI to use when calling ``fptr``. Some significant ones include:

doc/manual/methods.rst

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,119 @@ Julia its ability to abstractly express high-level algorithms decoupled
265265
from implementation details, yet generate efficient, specialized code to
266266
handle each case at run time.
267267

268+
Redefining Methods
269+
------------------
270+
271+
When redefining a method or adding new methods,
272+
it is important to realize that these changes don't take effect immediately.
273+
This is key to Julia's ability to statically infer and compile code to run fast,
274+
without the usual JIT tricks and overhead.
275+
Indeed, any new method definition won't be visible to the current runtime environment,
276+
including Tasks and Threads, or any previously defined ``@generated`` functions.
277+
Let's start with an example to see what this means::
278+
279+
julia> function tryeval()
280+
@eval newfun() = 1
281+
newfun()
282+
end
283+
tryeval (generic function with 1 method)
284+
285+
julia> tryeval()
286+
ERROR: MethodError: no method matching newfun()
287+
The applicable method may be too new: running in world age xxxx1, while current world is xxxx2.
288+
Closest candidates are:
289+
newfun() at none:1 (method too new to be called from this world context.)
290+
in tryeval() at none:1
291+
...
292+
293+
julia> newfun()
294+
1
295+
296+
In this example, observe that the new definition for ``newfun`` has been created,
297+
but can't be immediately called. Future calls to ``newfun`` from the REPL work as expected.
298+
But future calls to ``tryeval`` will continue to see the definition of ``newfun`` as it was
299+
*at the previous statement at the REPL*. You may want to try this for yourself to see how it works.
300+
301+
The world age counter is a monotonically increasing value that tracks each delayed operation.
302+
This allows describing "the set of method definitions visible to a runtime environment"
303+
as simply a number.
304+
It also allows comparing the methods available in two worlds just by comparing their ordinal value.
305+
In the example above, we see that the "current world" (in which the method ``newfun()`` exists),
306+
is one greater than the runtime world that was fixed when the execution of ``tryeval`` started.
307+
308+
Sometimes it is necessary to get around this (for example, if you are implementing the above REPL).
309+
Well, don't despair, since there's an easy solution: just call ``eval`` a second time.
310+
For example, here we create a zero-argument closure over ``ans`` and ``eval`` a call to it:
311+
312+
.. doctest::
313+
314+
julia> function tryeval2()
315+
ans = (@eval newfun2() = 1)
316+
res = eval(Expr(:call,
317+
function()
318+
return ans() + 1
319+
end))
320+
return res
321+
end
322+
tryeval2 (generic function with 1 method)
323+
324+
julia> tryeval2()
325+
2
326+
327+
Finally, let's take a look at some more complex examples where this rule comes into play.
328+
329+
.. doctest::
330+
331+
julia> # initially f(x) has one definition:
332+
333+
julia> f(x) = "original definition";
334+
335+
julia> # start some other operations that use f(x):
336+
337+
julia> g(x) = f(x);
338+
339+
julia> t = @async f(wait()); yield();
340+
341+
julia> @generated gen1(x) = f(x);
342+
343+
julia> @generated gen2(x) = :(f(x));
344+
345+
julia> # now we add some new definitions for f(x):
346+
347+
julia> f(x::Int) = "definition for Int";
348+
349+
julia> f(x::Type{Int}) = "definition for Type{Int}";
350+
351+
julia> # and compare how these results differ:
352+
353+
julia> f(1)
354+
"definition for Int"
355+
356+
julia> g(1)
357+
"definition for Int"
358+
359+
julia> wait(schedule(t, 1))
360+
"original definition"
361+
362+
julia> t = @async f(wait()); yield();
363+
364+
julia> wait(schedule(t, 1))
365+
"definition for Int"
366+
367+
julia> gen1(1)
368+
"original definition"
369+
370+
julia> gen2(1)
371+
"definition for Int"
372+
373+
julia> # each method of a generated function has its own view of defined functions:
374+
375+
julia> @generated gen1(x::Real) = f(x);
376+
377+
julia> gen1(1)
378+
"definition for Type{Int}"
379+
380+
268381
Method Ambiguities
269382
------------------
270383

0 commit comments

Comments
 (0)