From 41911d96d86397168ade1f39914a8d7ed1653558 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Tue, 14 Jul 2015 21:02:00 +0100 Subject: [PATCH 1/2] Big overhaul of "Scope" manual section [ci skip] --- doc/manual/functions.rst | 1 + doc/manual/modules.rst | 14 +- doc/manual/noteworthy-differences.rst | 2 + doc/manual/variables-and-scoping.rst | 477 ++++++++++++++++---------- 4 files changed, 309 insertions(+), 185 deletions(-) diff --git a/doc/manual/functions.rst b/doc/manual/functions.rst index 8c7166edde293..b5bfe588cf244 100644 --- a/doc/manual/functions.rst +++ b/doc/manual/functions.rst @@ -495,6 +495,7 @@ tuple, explicitly after a semicolon. For example, ``plot(x, y; ``plot(x, y, width=2)``. This is useful in situations where the keyword name is computed at runtime. +.. _man-evaluation-scope-default-values: Evaluation Scope of Default Values ---------------------------------- diff --git a/doc/manual/modules.rst b/doc/manual/modules.rst index 09e2acb909653..3c3f2581d6ffc 100644 --- a/doc/manual/modules.rst +++ b/doc/manual/modules.rst @@ -6,12 +6,14 @@ .. index:: module, baremodule, using, import, export, importall -Modules in Julia are separate global variable workspaces. They are -delimited syntactically, inside ``module Name ... end``. Modules allow -you to create top-level definitions without worrying about name conflicts -when your code is used together with somebody else's. Within a module, you -can control which names from other modules are visible (via importing), -and specify which of your names are intended to be public (via exporting). +Modules in Julia are separate variable workspaces, i.e. they introduce +a new global scope. They are delimited syntactically, inside ``module +Name ... end``. Modules allow you to create top-level definitions (aka +global variables) without worrying about name conflicts when your code +is used together with somebody else's. Within a module, you can +control which names from other modules are visible (via importing), +and specify which of your names are intended to be public (via +exporting). The following example demonstrates the major features of modules. It is not meant to be run, but is shown for illustrative purposes:: diff --git a/doc/manual/noteworthy-differences.rst b/doc/manual/noteworthy-differences.rst index 0926b57c35ec6..730cc34acd98e 100644 --- a/doc/manual/noteworthy-differences.rst +++ b/doc/manual/noteworthy-differences.rst @@ -91,6 +91,8 @@ some noteworthy differences that may trip up Julia users accustomed to MATLAB: ``ans`` is not set when Julia code is run in non-interactive mode. - Julia's ``type``\ s do not support dynamically adding fields at runtime, unlike MATLAB's ``class``\ es. Instead, use a :obj:`Dict`. +- In Julia each module has its own global scope/namespace, whereas in + Matlab there is just one global scope. Noteworthy differences from R ----------------------------- diff --git a/doc/manual/variables-and-scoping.rst b/doc/manual/variables-and-scoping.rst index 8310b631f458a..4a408115ddf5e 100644 --- a/doc/manual/variables-and-scoping.rst +++ b/doc/manual/variables-and-scoping.rst @@ -6,234 +6,347 @@ Scope of Variables ******************** -The *scope* of a variable is the region of code within which a variable -is visible. Variable scoping helps avoid variable naming conflicts. The -concept is intuitive: two functions can both have arguments called ``x`` -without the two ``x``'s referring to the same thing. Similarly there are -many other cases where different blocks of code can use the same name -without referring to the same thing. The rules for when the same -variable name does or doesn't refer to the same thing are called scope -rules; this section spells them out in detail. +The *scope* of a variable is the region of code within which a +variable is visible. Variable scoping helps avoid variable naming +conflicts. The concept is intuitive: two functions can both have +arguments called ``x`` without the two ``x``'s referring to the same +thing. Similarly there are many other cases where different blocks of +code can use the same name without referring to the same thing. The +rules for when the same variable name does or doesn't refer to the +same thing are called scope rules; this section spells them out in +detail. Certain constructs in the language introduce *scope blocks*, which are regions of code that are eligible to be the scope of some set of -variables. The scope of a variable cannot be an arbitrary set of source -lines; instead, it will always line up with one of these blocks. -The constructs introducing such blocks are: - -- ``function`` bodies (:ref:`either syntax `) -- ``while`` loops -- ``for`` loops -- ``try`` blocks -- ``catch`` blocks -- ``finally`` blocks -- ``let`` blocks -- ``type`` blocks. - -Notably missing from this list are -:ref:`begin blocks ` and :ref:`if blocks `, which do -*not* introduce new scope blocks. - -Certain constructs introduce new variables into the current innermost -scope. When a variable is introduced into a scope, it is also inherited -by all inner scopes unless one of those inner scopes explicitly -overrides it. +variables. The scope of a variable cannot be an arbitrary set of +source lines; instead, it will always line up with one of these +blocks. There are two main types of scopes in Julia, *global scope* +and *local scope*, the latter can be nested. The constructs +introducing scope blocks are: + ++--------------------------------+----------------------------------------------------------------------------------+ +| Scope name | block/construct introducing this kind of scope | ++================================+==================================================================================+ +| :ref:`global ` | | module, baremodule, at interactive prompt (REPL) | ++--------------------------------+------------------------------+---------------------------------------------------+ +| :ref:`local ` | :ref:`soft ` | | for, while, list-comprehensions, | +| | | try-catch-finally, let | +| +------------------------------+---------------------------------------------------+ +| | :ref:`hard `| | functions (either syntax, anonymous & do-blocks)| +| | | | type, immutable, macro | ++--------------------------------+------------------------------+---------------------------------------------------+ + +Notably missing from this table are :ref:`begin blocks +` and :ref:`if blocks +`, which do *not* introduce new scope +blocks. All three types of scopes follow somewhat different rules +which will be explained below as well as some extra rules for +certain blocks. Julia uses `lexical scoping `_, meaning that a function's scope does not inherit from its caller's scope, but from the scope in which the function was defined. -For example, in the following code the ``x`` inside ``foo`` is found -in the global scope (and if no global variable ``x`` existed, an -undefined variable error would be raised):: +For example, in the following code the ``x`` inside ``foo`` is refers +to the ``x`` in the global scope of its module ``Bar``:: - function foo() - x + module Bar + x = 1 + foo() = x end - function bar() - x = 1 - foo() - end +and not a ``x`` in the scope where ``foo`` is used:: - x = 2 + julia> import Bar - julia> bar() - 2 + julia> x = -1; -If ``foo`` is instead defined inside ``bar``, then it accesses -the local ``x`` present in that function:: + julia> Bar.foo() + 1 - function bar() - function foo() - x - end - x = 1 - foo() +Thus *lexical scope* means that the scope of variables can be inferred +from the source code alone. + +.. _man-global: + +Global Scope +------------ + +*Each module introduces a new global scope*, separate from the global +scope of all other modules; there is no all-encompassing global scope. +Modules can introduce variables of other modules into their scope +through the :ref:`using or import ` statements or through +qualified access using the dot-notation, i.e. each module is a +so-called *namespace*. Note that variable bindings can only be +changed within their global scope and not from an outside module. :: + + module A + a = 1 # a global in A's scope end - x = 2 + module B + # b = a # would error as B's global scope is separate from A's + module C + c = 2 + end + b = C.c # can access the namespace of a nested global scope + # through a qualified access + import A # makes module A available + d = A.a + # A.a = 2 # would error with: "ERROR: type Module has no field a" + end - julia> bar() - 1 +Note that the interactive prompt (aka REPL) is in the global scope of +the module ``Main``. -The constructs that introduce new variables into the current scope -are as follows: - -- A declaration ``local x`` or ``const x`` introduces a new local variable. -- A declaration ``global x`` makes ``x`` in the current scope and inner - scopes refer to the global variable of that name. -- A function's arguments are introduced as new local variables into the - function's body scope. -- An assignment ``x = y`` introduces a new local variable ``x`` only if - ``x`` is neither declared global nor introduced as local - by any enclosing scope before *or after* the current line of code. - -In the following example, there is only one ``x`` assigned both inside -and outside the ``for`` loop:: - - function foo(n) - x = 0 - for i = 1:n - x = x + 1 - end - x +.. _man-local-scope: + +Local Scope +----------- + +A local scope *usually* inherits all the variables from its parent +scope, both for reading and writing. There are two subtypes of local +scopes, hard and soft, with slightly different rules concerning what +variables are inherited. Unlike global scopes, local scopes are not +namespaces, thus variables in an inner scope cannot be retrieved from +the parent scope through some sort of qualified access. + +The following rules and examples pertain to both hard and soft local +scopes. A newly introduced variable in a local scope does not +back-propagate to its parent scope. For example, here the ``z`` is not +introduced into the top-level scope:: + + for i=1:10 + z = i end - julia> foo(10) - 10 + julia> z + ERROR: UndefVarError: z not defined + +(Note, in this and all following examples it is assumed that their +top-level is a global scope with a clean workspace, for instance a +newly started REPL.) -In the next example, the loop has a separate ``x`` and the function -always returns zero:: +Inside a local scope a variable can be forced to be a local variable +using the ``local`` keyword:: - function foo(n) - x = 0 - for i = 1:n + x = 0 + for i=1:10 local x - x = i - end - x + x = i + 1 end - julia> foo(10) + julia> x 0 -In this example, an ``x`` exists only inside the loop, and the function -encounters an undefined variable error on its last line (unless there is -a global variable ``x``):: +Inside a local scope a new global variable can be defined using the +keyword ``global``:: - function foo(n) - for i = 1:n - x = i - end - x + for i=1:10 + global z + z = i end - julia> foo(10) - in foo: x not defined - -A variable that is not assigned to or otherwise introduced locally -defaults to global, so this function would return the value of the -global ``x`` if there were such a variable, or produce an error if no such -global existed. As a consequence, the only way to assign to a global -variable inside a non-top-level scope is to explicitly declare the -variable as global within some scope, since otherwise the assignment -would introduce a new local rather than assigning to the global. This -rule works out well in practice, since the vast majority of variables -assigned inside functions are intended to be local variables, and using -global variables should be the exception rather than the rule, -and assigning new values to them even more so. - -One last example shows that an outer assignment introducing ``x`` need -not come before an inner usage:: - - function foo(n) - f = y -> n + x + y - x = 1 - f(2) + julia> z + 10 + +.. + However, there is no keyword to introduce a new local variable into a + parent local scope. + +The location of both the ``local`` and ``global`` keywords within the +scope block is irrelevant. The following is equivalent to the last +example (although stylistically worse):: + + for i=1:10 + z = i + global z end - julia> foo(10) - 13 + julia> z + 10 -This behavior may seem slightly odd for a normal variable, but allows -for named functions — which are just normal variables holding function -objects — to be used before they are defined. This allows functions to -be defined in whatever order is intuitive and convenient, rather than -forcing bottom up ordering or requiring forward declarations, both of -which one typically sees in C programs. As an example, here is an -inefficient, mutually recursive way to test if positive integers are -even or odd:: +Multiple global or local definitions can be on one line and can also +be paired with assignments:: - even(n) = n == 0 ? true : odd(n-1) - odd(n) = n == 0 ? false : even(n-1) + for i=1:10 + global x=i, y, z + local a=4, b , c=1 + end - julia> even(3) - false - julia> odd(3) - true +.. _man-soft-scope: -Julia provides built-in, efficient functions to test for oddness and evenness -called :func:`iseven` and :func:`isodd` so the above definitions should only be -taken as examples. +Soft Local Scope +^^^^^^^^^^^^^^^^ -Since functions can be used before they are defined, as long as they are -defined by the time they are actually called, no syntax for forward -declarations is necessary, and definitions can be ordered arbitrarily. + In a soft local scope, all variables are inherited from its parent + scope unless a variable is specifically marked with the keyword + ``local``. -At the interactive prompt, variable scope works the same way as anywhere -else. The prompt behaves as if there is scope block wrapped around -everything you type, except that this scope block is identified with the -global scope. This is especially evident in the case of assignments: +Soft local scopes are introduced by for-loops, while-loops, +list-comprehensions, try-catch-finally-blocks, and let-blocks. There +are some extra rules for :ref:`let-blocks ` and for +:ref:`for-loops and list-comprehensions `. -.. doctest:: +In the following example the ``x`` and ``y`` refer always to the same +variables as the soft local scope inherits both read and write +variables:: - julia> for i = 1:1; y = 10; end + x,y = 0, 1 + for i = 1:10 + x = i + y + 1 + end - julia> y - ERROR: UndefVarError: y not defined + julia> x + 11 + +Within soft scopes, the `global` keyword is never necessary, although +allowed. The only case when it would change the semantics is +(currently) a syntax error:: + + x = 1 + let + local x = 2 + let + global x = 3 + end + end - julia> y = 0 - 0 + # ERROR: syntax: `global #1#x`: #1#x is local variable in the enclosing scope - julia> for i = 1:1; y = 10; end +.. _man-hard-scope: - julia> y - 10 +Hard Local Scope +^^^^^^^^^^^^^^^^ -In the former case, ``y`` only exists inside of the ``for`` loop. In the -latter case, an outer ``y`` has been introduced and so is inherited -within the loop. Due to the special identification of the prompt's scope -block with the global scope, it is not necessary to declare ``global y`` -inside the loop. However, in code not entered into the interactive -prompt this declaration would be necessary in order to modify a global -variable. +Hard local scopes are introduced by function definitions (in all their +forms), type & immutable-blocks and macro-definitions. -Multiple variables can be declared global using the following syntax:: + In a hard local scope, all variables are inherited from its parent + scope unless: + - an assignment would result in a modified *global* variable, or + - a variable is specifically marked with the keyword ``local``. + +Thus global variables are only inherited for reading but not for +writing:: + + x,y = 1,2 function foo() - global x=1, y="bar", z=3 + x = 2 # assignment introduces a new local + return x + y # y refers to the global end julia> foo() - 3 + 4 julia> x 1 - julia> y - "bar" +An explicit ``global`` is needed to assign to a global variable:: - julia> z - 3 + x = 1 + function foo() + global x = 2 + end + foo() + + julia> x + 2 + +However, nested functions can modify the parent's *local* variables:: + + x,y = 1,2 + function foo() + x = 2 # introduces a new local + function bar() + x = 10 # modifies the parent's x + return x+y # y is global + end + return bar() + x # 12 + 10 (x is modified in call of bar()) + end + + julia> foo() + 22 # x,y unchanged + +The distinction between inheriting global and local variables for +assignment can lead to some slight differences between functions +defined in local vs. global scopes. Consider the modification of the +last example by moving ``bar`` to the global scope:: + + x,y = 1,2 + function bar() + x = 10 # local + return x+y + end + function foo() + x = 2 # local + return bar() + x # 12 + 2 (x is not modified) + end + + julia> foo() + 14 # as x is not modified anymore + # x,y unchanged + +Note that above subtlety does not pertain to type and macro +definitions as they can only appear at the global scope. +There are special scoping rules concerning the evaluation of default +and keyword function arguments which are described in the +:ref:`Function section `. + + +An assignment introducing a variable used inside a function, type or +macro definition need not come before its inner usage: + +.. doctest:: + + julia> f = y -> x + y + (anonymous function) + + julia> f(3) + ERROR: UndefVarError: x not defined + in anonymous at none:1 + + julia> x = 1 + 1 + + julia> f(3) + 4 + +This behavior may seem slightly odd for a normal variable, but allows +for named functions — which are just normal variables holding function +objects — to be used before they are defined. This allows functions to +be defined in whatever order is intuitive and convenient, rather than +forcing bottom up ordering or requiring forward declarations, as long +as they are defined by the time they are actually called. As an +example, here is an inefficient, mutually recursive way to test if +positive integers are even or odd:: + + even(n) = n == 0 ? true : odd(n-1) + odd(n) = n == 0 ? false : even(n-1) + + julia> even(3) + false + + julia> odd(3) + true + +Julia provides built-in, efficient functions to test for oddness and evenness +called :func:`iseven` and :func:`isodd` so the above definitions should only be +taken as examples. + +.. _man-let-blocks: + +Let Blocks +^^^^^^^^^^ -The ``let`` statement provides a different way to introduce variables. Unlike assignments to local variables, ``let`` statements allocate new -variable bindings each time they run. An assignment modifies an existing -value location, and ``let`` creates new locations. This difference is -usually not important, and is only detectable in the case of variables -that outlive their scope via closures. The ``let`` syntax accepts a -comma-separated series of assignments and variable names:: +variable bindings each time they run. An assignment modifies an +existing value location, and ``let`` creates new locations. This +difference is usually not important, and is only detectable in the +case of variables that outlive their scope via closures. The ``let`` +syntax accepts a comma-separated series of assignments and variable +names:: let var1 = value1, var2, var3 = value3 code @@ -245,7 +358,7 @@ has been introduced. Therefore it makes sense to write something like ``let x = x`` since the two ``x`` variables are distinct and have separate storage. Here is an example where the behavior of ``let`` is needed:: - Fs = cell(2) + Fs = Array(Any,2) i = 1 while i <= 2 Fs[i] = ()->i @@ -263,7 +376,7 @@ However, it is always the same variable ``i``, so the two closures behave identically. We can use ``let`` to create a new binding for ``i``:: - Fs = cell(2) + Fs = Array(Any,2) i = 1 while i <= 2 let i = i @@ -296,15 +409,21 @@ block without creating any new bindings: Since ``let`` introduces a new scope block, the inner local ``x`` is a different variable than the outer local ``x``. + +.. _man-for-loops-scope: + For Loops and Comprehensions ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + -``for`` loops and :ref:`comprehensions ` have a special -additional behavior: any new variables introduced in their body scopes are -freshly allocated for each loop iteration. Therefore these constructs are -similar to ``while`` loops with ``let`` blocks inside:: +``for`` loops and :ref:`comprehensions ` have the +following behavior: any new variables introduced in their body scopes +are freshly allocated for each loop iteration. This is in contrast to +``while`` loops which reuse the variables for all +iterations. Therefore these constructs are similar to ``while`` loops +with ``let`` blocks inside:: - Fs = cell(2) + Fs = Array(Any,2) for i = 1:2 Fs[i] = ()->i end @@ -315,7 +434,7 @@ similar to ``while`` loops with ``let`` blocks inside:: julia> Fs[2]() 2 -``for`` loops will reuse existing variables for iteration:: +``for`` loops will reuse existing variables for its iteration variable:: i = 0 for i = 1:3 @@ -327,7 +446,7 @@ iteration variables:: x = 0 [ x for x=1:3 ] - x # here still equal to 0 + X # here still equal to 0 Constants --------- From abdfc24ac1a7acd67b474a1848758589d4613065 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Thu, 16 Jul 2015 14:55:25 +0200 Subject: [PATCH 2/2] Some updates after a mailing-list conversation [ci skip] --- doc/manual/variables-and-scoping.rst | 65 ++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/doc/manual/variables-and-scoping.rst b/doc/manual/variables-and-scoping.rst index 4a408115ddf5e..802d706b1bf54 100644 --- a/doc/manual/variables-and-scoping.rst +++ b/doc/manual/variables-and-scoping.rst @@ -24,6 +24,8 @@ blocks. There are two main types of scopes in Julia, *global scope* and *local scope*, the latter can be nested. The constructs introducing scope blocks are: +.. _man-scope-table: + +--------------------------------+----------------------------------------------------------------------------------+ | Scope name | block/construct introducing this kind of scope | +================================+==================================================================================+ @@ -32,7 +34,7 @@ introducing scope blocks are: | :ref:`local ` | :ref:`soft ` | | for, while, list-comprehensions, | | | | try-catch-finally, let | | +------------------------------+---------------------------------------------------+ -| | :ref:`hard `| | functions (either syntax, anonymous & do-blocks)| +| | :ref:`hard ` | | functions (either syntax, anonymous & do-blocks)| | | | | type, immutable, macro | +--------------------------------+------------------------------+---------------------------------------------------+ @@ -103,12 +105,14 @@ the module ``Main``. Local Scope ----------- -A local scope *usually* inherits all the variables from its parent -scope, both for reading and writing. There are two subtypes of local -scopes, hard and soft, with slightly different rules concerning what -variables are inherited. Unlike global scopes, local scopes are not -namespaces, thus variables in an inner scope cannot be retrieved from -the parent scope through some sort of qualified access. +A new local scope is introduced by most code-blocks, see above +:ref:`table ` for a complete list. A local scope +*usually* inherits all the variables from its parent scope, both for +reading and writing. There are two subtypes of local scopes, hard and +soft, with slightly different rules concerning what variables are +inherited. Unlike global scopes, local scopes are not namespaces, +thus variables in an inner scope cannot be retrieved from the parent +scope through some sort of qualified access. The following rules and examples pertain to both hard and soft local scopes. A newly introduced variable in a local scope does not @@ -204,7 +208,6 @@ Within soft scopes, the `global` keyword is never necessary, although allowed. The only case when it would change the semantics is (currently) a syntax error:: - x = 1 let local x = 2 let @@ -212,7 +215,7 @@ allowed. The only case when it would change the semantics is end end - # ERROR: syntax: `global #1#x`: #1#x is local variable in the enclosing scope + # ERROR: syntax: `global x`: x is local variable in the enclosing scope .. _man-hard-scope: @@ -254,7 +257,9 @@ An explicit ``global`` is needed to assign to a global variable:: julia> x 2 -However, nested functions can modify the parent's *local* variables:: +Note that *nested functions* can behave differently to functions +defined in the global scope as they can modify their parent scope's +*local* variables:: x,y = 1,2 function foo() @@ -267,7 +272,7 @@ However, nested functions can modify the parent's *local* variables:: end julia> foo() - 22 # x,y unchanged + 22 # (x,y unchanged) The distinction between inheriting global and local variables for assignment can lead to some slight differences between functions @@ -285,8 +290,8 @@ last example by moving ``bar`` to the global scope:: end julia> foo() - 14 # as x is not modified anymore - # x,y unchanged + 14 # as x is not modified anymore. + # (x,y unchanged) Note that above subtlety does not pertain to type and macro definitions as they can only appear at the global scope. @@ -335,6 +340,40 @@ Julia provides built-in, efficient functions to test for oddness and evenness called :func:`iseven` and :func:`isodd` so the above definitions should only be taken as examples. +Hard vs. Soft Local Scope +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Blocks which introduce a soft local scope, such as loops, are +generally used to manipulate the variables in their parent scope. +Thus their default is to fully access all variables in their parent +scope. + +Conversely, the code inside blocks which introduce a hard local scope +(function, type and macro definitions) can be executed at any place in +a program. Remotely changing the state of global variables in other +modules should be done with care and thus this is an opt-in feature +requiring the ``global`` keyword. + +The reason to allow *modifying local* variables of parent scopes in +nested functions is to allow constructing `closures +`_ +which have a private state, for instance the ``state`` variable in the +following example:: + + let + state = 0 + global counter + counter() = state += 1 + end + + julia> counter() + 1 + + julia> counter() + 2 + +See also the closures in the examples in the next two sections. + .. _man-let-blocks: Let Blocks