Spec-Zone.ru › Julia 1.9

Область видимости переменных

Область видимости переменной — это область кода, в которой переменная доступна. Область видимости переменных помогает избежать конфликтов имён переменных. Концепция интуитивна: две функции могут иметь аргументы, названные x без того, чтобы два x ссылались на одну и ту же вещь. Аналогично, существуют и другие случаи, когда разные блоки кода могут использовать одно и то же имя без ссылки на одну и ту же вещь. Правила, определяющие, ссылается ли одно и то же имя переменной на одну и ту же вещь или нет, называются правилами области видимости; в этом разделе они подробно изложены.

Некоторые конструкции языка вводят блоки области видимости, которые представляют собой области кода, которые могут быть областью видимости некоторого набора переменных. Область видимости переменной не может быть произвольным набором строк исходного кода; вместо этого она всегда соответствует одному из этих блоков. В Julia существуют два основных типа областей видимости: глобальная область видимости и локальная область видимости. Последняя может быть вложенной. В Julia также существует различие между конструкциями, которые вводят «жёсткую область видимости», и теми, которые вводят только «мягкую область видимости», что влияет на то, разрешено ли затенение глобальной переменной с тем же именем или нет.

Конструкции области видимости

Конструкции, вводящие блоки области видимости:

Конструкция Тип области видимости Разрешено внутри
module, baremodule глобальная глобальная
struct локальная (мягкая) глобальная
for, while, try локальная (мягкая) глобальная, локальная
macro локальная (жёсткая) глобальная
функции, блоки do, блоки let, comprehension, генераторы локальная (жёсткая) глобальная, локальная

Обратите внимание, что в этой таблице отсутствуют блоки begin и if, которые не вводят новые области видимости. Три типа областей видимости подчиняются несколько разным правилам, которые будут объяснены ниже.

Julia использует лексическую область видимости, что означает, что область видимости функции не наследуется от области видимости вызывающей функции, а от области видимости, в которой функция была определена. Например, в следующем коде x внутри foo относится к x в глобальной области видимости её модуля Bar:

julia> module Bar
           x = 1
           foo() = x
       end;

а не к x в области видимости, где используется foo:

julia> import .Bar

julia> x = -1;

julia> Bar.foo()
1

Таким образом, лексическая область видимости означает, что то, на что переменная в конкретном фрагменте кода ссылается, может быть выведено из кода, в котором она появляется, и не зависит от того, как выполняется программа. Область видимости, вложенная внутри другой области видимости, может «видеть» переменные во всех внешних областях видимости, в которых она содержится. Внешние области видимости, в свою очередь, не могут видеть переменные во внутренних областях видимости.

Глобальная область видимости

Каждый модуль вводит новую глобальную область видимости, отделённую от глобальной области видимости всех других модулей — нет всеобъемлющей глобальной области видимости. Модули могут вводить переменные других модулей в свою область видимости с помощью операторов using или import или с помощью квалифицированного доступа, используя нотацию точек, т.е. каждый модуль является так называемым пространством имён, а также структурой данных первого класса, сопоставляющей имена значениям. Обратите внимание, что хотя внешние переменные могут быть прочитаны, их можно изменить только внутри того модуля, к которому они относятся. Как крайний вариант, вы всегда можете оценить код внутри этого модуля, чтобы изменить переменную; это гарантирует, что привязки к модулям не могут быть изменены внешним кодом, который никогда не вызывает eval.

julia> module A
           a = 1 # a global in A's scope
       end;

julia> module B
           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
       end;

julia> module D
           b = a # errors as D's global scope is separate from A's
       end;
ERROR: UndefVarError: `a` not defined

Если выражение верхнего уровня содержит объявление переменной с ключевым словом local, то эта переменная недоступна за пределами этого выражения. Переменная внутри выражения не влияет на глобальные переменные с тем же именем. Примером является объявление local x в блоке begin или if на верхнем уровне:

julia> x = 1
       begin
           local x = 0
           @show x
       end
       @show x;
x = 0
x = 1

Обратите внимание, что интерактивный командный интерпретатор (также известный как REPL) находится в глобальной области видимости модуля Main.

Локальная область видимости

Новая локальная область видимости вводится большинством блоков кода (см. выше таблицу таблицу для полного списка). Если такой блок синтаксически вложен внутри другой локальной области видимости, создаваемая им область видимости вкладывается во все локальные области видимости, внутри которых она находится, которые в конечном итоге все вложены в глобальную область видимости модуля, в котором код оценивается. Переменные во внешних областях видимости видны из любой содержащей их области видимости — это означает, что их можно читать и записывать во внутренних областях видимости — за исключением случаев, когда существует локальная переменная с тем же именем, что и внешняя переменная с тем же именем, которая «затеняет» внешнюю переменную с тем же именем. Это верно даже если внешняя локальная переменная объявлена после (в текстовом смысле ниже) внутреннего блока. Когда мы говорим, что переменная «существует» в данной области видимости, это означает, что переменная с таким именем существует в любой из областей видимости, в которую текущая область видимости вложена, включая текущую.

Некоторые языки программирования требуют явного объявления новых переменных перед их использованием. Явное объявление работает и в Julia: в любой локальной области видимости запись local x объявляет новую локальную переменную в этой области видимости, независимо от того, существует ли переменная с именем x во внешней области видимости или нет. Однако объявление каждой новой переменной таким образом довольно громоздко и утомительно, поэтому Julia, как и многие другие языки, рассматривает присваивание имени переменной, которая ещё не существует, как неявное объявление этой переменной. Если текущая область видимости глобальная, то новая переменная глобальная; если текущая область видимости локальная, то новая переменная локальная для самой внутренней локальной области видимости и будет видна внутри этой области видимости, но не снаружи. Если вы присваиваете значение существующей локальной переменной, то всегда обновляется эта существующая локальная переменная: вы можете затенить локальную переменную только явно объявив новую локальную переменную во вложенной области видимости с ключевым словом local. В частности, это относится к переменным, присвоенным во вложенных функциях, что может удивить пользователей, приходящих из Python, где присвоение во вложенной функции создаёт новую локальную переменную, если переменная не объявлена явно как нелокальная.

В основном это довольно интуитивно понятно, но, как и во многих вещах, которые интуитивно понятны, детали более тонки, чем можно было бы наивно предположить.

Когда x = <value> происходит в локальной области видимости, Julia применяет следующие правила, чтобы определить, что означает выражение, в зависимости от того, где происходит операция присваивания, и на что x уже ссылается в этой позиции:

  1. Существующая локальная переменная: Если x уже является локальной переменной, то существующей локальной переменной x присваивается значение;
  2. Жёсткая область видимости: Если x ещё не является локальной переменной и присваивание происходит внутри любой конструкции с жёсткой областью видимости (т.е. внутри блока let, тела функции или макроса, comprehension или генератора), создаётся новая локальная переменная с именем x в области видимости присваивания;
  3. Мягкая область видимости: Если x ещё не является локальной переменной и все конструкции области видимости, содержащие присваивание, являются мягкими областями видимости (циклы, блоки try/catch, или блоки struct), поведение зависит от того, определена ли глобальная переменная x:
    • если глобальная переменная x не определена, создаётся новая локальная переменная с именем x в области видимости присваивания;
    • если глобальная переменная x определена, присваивание считается неоднозначным:
      • в неинтерактивных контекстах (файлы, eval) выводится предупреждение об неоднозначности, и создаётся новая локальная переменная;
      • в интерактивных контекстах (REPL, ноутбуки) глобальной переменной x присваивается значение.

Вы можете заметить, что в неинтерактивных контекстах поведение жёсткой и мягкой области видимости идентично, за исключением того, что выводится предупреждение, когда неявно локальная переменная (т.е. не объявлена с local x) затеняет глобальную. В интерактивных контекстах правила следуют более сложной эвристике для удобства. Это подробно рассмотрено в примерах ниже.

Теперь, когда вы знаете правила, давайте посмотрим на некоторые примеры. Предполагается, что каждый пример оценивается в новой сессии REPL, чтобы единственными глобальными переменными в каждом фрагменте кода были переменные, присвоенные в этом блоке кода.

Начнём с ясной и понятной ситуации — присваивание внутри жёсткой области видимости, в данном случае тела функции, когда локальная переменная с таким именем ещё не существует:

julia> function greet()
           x = "hello" # new local
           println(x)
       end
greet (generic function with 1 method)

julia> greet()
hello

julia> x # global
ERROR: UndefVarError: `x` not defined

Внутри функции greet присваивание x = "hello" приводит к тому, что x становится новой локальной переменной в области видимости функции. Существуют два важных факта: присваивание происходит в локальной области видимости и нет существующей локальной переменной x. Поскольку x локальная, не имеет значения, существует ли глобальная переменная с именем x или нет. Например, здесь мы определяем x = 123 перед определением и вызовом greet:

julia> x = 123 # global
123

julia> function greet()
           x = "hello" # new local
           println(x)
       end
greet (generic function with 1 method)

julia> greet()
hello

julia> x # global
123

Поскольку x в greet локальная, значение (или его отсутствие) глобальной переменной x не изменяется при вызове greet. Правило жёсткой области видимости не учитывает, существует ли глобальная переменная с именем x или нет: присваивание x в жёсткой области видимости является локальным (если x объявлена глобальной).

Следующая ясная ситуация, которую мы рассмотрим, это когда уже существует локальная переменная, названная x, в этом случае x = <value> всегда присваивает значение этой существующей локальной x. Это верно, независимо от того, происходит ли присвоение в том же локальном пространстве, во внутреннем локальном пространстве в том же теле функции или в теле функции, вложенной в другую функцию, также известной как замыкание.

Мы будем использовать функцию sum_to, которая вычисляет сумму целых чисел от единицы до n, в качестве примера:

function sum_to(n)
    s = 0 # new local
    for i = 1:n
        s = s + i # assign existing local
    end
    return s # same local
end

Как и в предыдущем примере, первое присвоение s в верхней части sum_to приводит к тому, что s становится новой локальной переменной в теле функции. У цикла for есть собственное внутреннее локальное пространство внутри пространства функции. В тот момент, когда происходит s = s + i, s уже является локальной переменной, поэтому присвоение обновляет существующую s вместо создания новой локальной. Мы можем проверить это, вызвав sum_to в REPL:

julia> function sum_to(n)
           s = 0 # new local
           for i = 1:n
               s = s + i # assign existing local
           end
           return s # same local
       end
sum_to (generic function with 1 method)

julia> sum_to(10)
55

julia> s # global
ERROR: UndefVarError: `s` not defined

Поскольку s локальна для функции sum_to, вызов функции не влияет на глобальную переменную s. Мы также видим, что обновление s = s + i в цикле for должно было обновить ту же s, созданную инициализацией s = 0, поскольку мы получаем правильную сумму 55 для целых чисел от 1 до 10.

Давайте углубимся в тот факт, что тело цикла for имеет собственное пространство, написав немного более подробную вариацию, которую мы назовем sum_to_def, в которой мы сохраняем сумму s + i в переменной t перед обновлением s.

julia> function sum_to_def(n)
           s = 0 # new local
           for i = 1:n
               t = s + i # new local `t`
               s = t # assign existing local `s`
           end
           return s, @isdefined(t)
       end
sum_to_def (generic function with 1 method)

julia> sum_to_def(10)
(55, false)

Эта версия возвращает s как и прежде, но она также использует макрос @isdefined для возврата булевого значения, указывающего, существует ли локальная переменная, названная t, определенная во внешнем локальном пространстве функции. Как вы можете видеть, t не определен вне тела цикла for. Это связано с правилом жёсткого пространства: поскольку присвоение t происходит внутри функции, что вводит жёсткое пространство, присвоение приводит к тому, что t становится новой локальной переменной в локальном пространстве, где оно появляется, т. е. внутри тела цикла. Даже если бы существовала глобальная переменная, названная t, это не имело бы значения — правило жёсткого пространства не зависит ни от чего в глобальном пространстве.

Обратите внимание, что локальное пространство тела цикла for ничем не отличается от локального пространства вложенной функции. Это означает, что мы могли бы переписать этот пример таким образом, чтобы тело цикла было реализовано как вызов вложенной вспомогательной функции, и оно работало бы таким же образом:

julia> function sum_to_def_closure(n)
           function loop_body(i)
               t = s + i # new local `t`
               s = t # assign same local `s` as below
           end
           s = 0 # new local
           for i = 1:n
               loop_body(i)
           end
           return s, @isdefined(t)
       end
sum_to_def_closure (generic function with 1 method)

julia> sum_to_def_closure(10)
(55, false)

Этот пример иллюстрирует несколько ключевых моментов:

  1. Вложенные пространства функций такие же, как и любое другое вложенное локальное пространство. В частности, если переменная уже является локальной переменной вне вложенной функции, и вы присваиваете ей значение во вложенной функции, то внешняя локальная переменная обновляется.

  2. Неважно, происходит ли определение внешней локальной переменной ниже, чем её обновление, правило остается прежним. Всё окружающее локальное пространство анализируется, и его локальные переменные определяются до того, как будут разрешены внутренние локальные значения.

Этот дизайн означает, что вы, как правило, можете перемещать код в или из вложенной функции, не изменяя его смысл, что облегчает реализацию ряда распространённых идиом в языке, использующих замыкания (см. блоки do).

Перейдём к некоторым более неоднозначным случаям, охватываемым правилом мягкого пространства. Мы изучим это, извлекая тела функций greet и sum_to_def в контексты мягкого пространства. Сначала давайте поместим тело greet в цикл for — который является мягким, а не жёстким — и оценим его в REPL:

julia> for i = 1:3
           x = "hello" # new local
           println(x)
       end
hello
hello
hello

julia> x
ERROR: UndefVarError: `x` not defined

Так как глобальная переменная x не определена, когда оценивается цикл for, выполняется первая часть правила мягкого пространства, и x создаётся как локальная переменная для цикла for, и поэтому глобальная переменная x остаётся неопределённой после выполнения цикла. Далее, рассмотрим тело sum_to_def и перенесём его в глобальное пространство, зафиксировав его аргумент на n = 10

s = 0
for i = 1:10
    t = s + i
    s = t
end
s
@isdefined(t)

Что делает этот код? Подсказка: это вопрос-ловушка. Ответ: "это зависит". Если этот код вводится интерактивно, он ведёт себя так же, как и в теле функции. Но если код появляется в файле, он выводит предупреждение об неоднозначности и вызывает ошибку неопределённой переменной. Давайте сначала посмотрим, как это работает в REPL:

julia> s = 0 # global
0

julia> for i = 1:10
           t = s + i # new local `t`
           s = t # assign global `s`
       end

julia> s # global
55

julia> @isdefined(t) # global
false

REPL приближённо моделирует нахождение внутри тела функции, определяя, присваивает ли присвоение внутри цикла значение глобальной переменной или создаёт новую локальную переменную, в зависимости от того, определена ли глобальная переменная с этим именем или нет. Если глобальная переменная с таким именем существует, то присвоение обновляет её. Если глобальная переменная не существует, то присвоение создаёт новую локальную переменную. В этом примере мы видим оба случая в действии:

  • Глобальная переменная с именем t не определена, поэтому t = s + i создаёт новую t, которая является локальной для цикла for;
  • Глобальная переменная с именем s определена, поэтому s = t присваивает значение ей.

Второй факт объясняет, почему выполнение цикла изменяет глобальное значение s, а первый факт объясняет, почему t всё ещё неопределён после выполнения цикла. Теперь давайте попробуем оценить тот же код, как будто он находится в файле:

julia> code = """
       s = 0 # global
       for i = 1:10
           t = s + i # new local `t`
           s = t # new local `s` with warning
       end
       s, # global
       @isdefined(t) # global
       """;

julia> include_string(Main, code)
┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable.
└ @ string:4
ERROR: LoadError: UndefVarError: `s` not defined

Здесь мы используем include_string, чтобы оценить code так, как будто это содержимое файла. Мы также могли бы сохранить code в файле, а затем вызвать include на этом файле — результат был бы таким же. Как вы можете видеть, это ведёт себя совершенно по-другому, чем оценивание того же кода в REPL. Давайте разберём, что происходит здесь:

  • глобальная переменная s определена со значением 0 до оценки цикла
  • присвоение s = t происходит в мягком пространстве — в цикле for вне любого тела функции или другой структуры жёсткого пространства
  • следовательно, выполняется вторая часть правила мягкого пространства, и присвоение является неоднозначным, поэтому выводится предупреждение
  • выполнение продолжается, делая s локальной для тела цикла for
  • поскольку s локальна для цикла for, она неопределённа, когда оценивается t = s + i, что вызывает ошибку
  • вычисление останавливается там, но если бы оно добралось до s и @isdefined(t), оно бы вернуло 0 и false.

Это демонстрирует некоторые важные аспекты пространства: в пространстве каждая переменная может иметь только одно значение, и это значение определяется независимо от порядка выражений. Наличие выражения s = t в цикле приводит к тому, что s является локальной переменной для цикла, что означает, что она также является локальной, когда она появляется в правой части t = s + i, даже если это выражение появляется раньше и оценивается первым. Можно представить, что s на первой строке цикла может быть глобальной, а s на второй строке цикла — локальной, но это невозможно, так как две строки находятся в одном блоке пространства, и каждая переменная может иметь только одно значение в данном пространстве.

О мягком пространстве

Мы теперь рассмотрели все правила локального пространства, но прежде чем завершить этот раздел, возможно, стоит сказать несколько слов о том, почему неоднозначный случай мягкого пространства обрабатывается по-разному в интерактивных и неинтерактивных контекстах. Возникают два очевидных вопроса:

  1. Почему это не работает так же везде, как в REPL?
  2. Почему это не работает так же, как в файлах везде? И возможно, пропустить предупреждение?

В Julia ≤ 0.6 все глобальные пространства работали как текущий REPL: когда x = <value> встречался в цикле (или try/catch, или struct тела), но вне тела функции (или let блока или понимания), определялось, должно ли x быть локальным для цикла, в зависимости от того, определена ли глобальная переменная с именем x или нет. Эта функция имеет преимущество интуитивности и удобства, так как максимально приближена к поведению внутри тела функции. В частности, это упрощает перемещение кода между телом функции и REPL при отладке поведения функции. Однако, она имеет некоторые недостатки. Во-первых, это довольно сложное поведение: многие люди на протяжении многих лет были сбиты с толку этим поведением и жаловались, что оно сложное, сложно как объяснить, так и понять. Правильно. Во-вторых, и, возможно, хуже, это плохо для программирования "в масштабе". Когда вы видите небольшой фрагмент кода в одном месте, как это, довольно ясно, что происходит:

s = 0
for i = 1:10
    s += i
end

Очевидно, целью является изменение существующей глобальной переменной s. Что ещё это может означать? Однако не весь реальный код такой короткий или понятный. Мы обнаружили, что код, подобный следующему, часто встречается в дикой природе:

x = 123

# much later
# maybe in a different file

for i = 1:10
    x = "hello"
    println(x)
end

# much later
# maybe in yet another file
# or maybe back in the first one where `x = 123`

y = x + 234

Гораздо менее очевидно, что должно произойти здесь. Поскольку x + "hello" — это ошибка метода, кажется вероятным, что цель состоит в том, чтобы x была локальной для цикла for. Но значения времени выполнения и методы, которые, возможно, существуют, не могут быть использованы для определения пространства переменных. С поведением Julia ≤ 0.6 особенно беспокоительно, что кто-то мог написать цикл for первым, заставив его работать, но позже, когда кто-то добавляет новую глобальную переменную далеко — возможно, в другом файле — код внезапно меняет смысл и либо ломается с ошибкой, либо, что ещё хуже, тихо выполняет неверные действия. Такая "сверхъестественная сила действия на расстоянии" — это то, чему хорошему программированию на языке следует препятствовать.

Таким образом, в Julia 1.0 мы упростили правила для пространства: в любом локальном пространстве присвоение имени, которое не было уже локальной переменной, создавало новую локальную переменную. Это полностью устранило понятие мягкого пространства, а также устранило потенциальную возможность сверхъестественного действия. Мы обнаружили и исправили значительное количество ошибок из-за удаления мягкого пространства, подтверждая выбор избавиться от него. И тут начался настоящий праздник! Ну, не совсем. Потому что некоторые люди были в ярости, что теперь им нужно было написать:

s = 0
for i = 1:10
    global s += i
end

Видите аннотацию global там? Ужасно. Очевидно, что эта ситуация не могла быть терпимой. Но серьезно, есть две основные проблемы с требованием global для этого типа кода верхнего уровня:

  1. Теперь неудобно копировать и вставлять код из тела функции в REPL для отладки — вам нужно добавить аннотации global и затем снова удалить их, чтобы вернуться;

  2. Начинающие программисты будут писать этот тип кода без global и не будут знать, почему их код не работает — ошибка, которую они получат, заключается в том, что s не определено, что, похоже, не проясняет ситуацию никому, кто совершает эту ошибку.

Начиная с Julia 1.5, этот код работает без аннотации global в интерактивных контекстах, таких как REPL или Jupyter-тетради (как в Julia 0.6), а в файлах и других неинтерактивных контекстах он выводит следующее прямое предупреждение:

Присвоение s в мягкой области видимости неоднозначно, потому что глобальная переменная с тем же именем существует: s будет обработана как новая локальная переменная. Устраните неоднозначность, используя local s для подавления этого предупреждения или global s для присвоения существующей глобальной переменной.

Это решает обе проблемы, сохраняя при этом преимущества «программирования в масштабе» поведения 1.0: глобальные переменные не оказывают странного влияния на значение кода, который может находиться далеко; в REPL работает отладка методом копирования-вставки, и начинающие программисты не сталкиваются с проблемами; каждый раз, когда кто-то забывает аннотацию global или случайно затеняет существующую глобальную переменную локальной переменной в мягкой области видимости, что само по себе было бы запутанным, они получат ясное и понятное предупреждение.

Важным свойством этой конструкции является то, что любой код, который выполняется в файле без предупреждений, будет вести себя так же в свежем REPL. И наоборот, если вы сохраните сеанс REPL в файл, если он будет вести себя иначе, чем в REPL, то вы получите предупреждение.

Блоки Let

let операторы создают новый блок жесткой области видимости (см. выше) и вводят новые привязки переменных каждый раз, когда они выполняются. Переменная не обязательно должна быть немедленно присвоена:

julia> var1 = let x
           for i in 1:5
               (i == 4) && (x = i; break)
           end
           x
       end
4

В то время как присвоения могут повторно назначать новое значение существующему расположению значения, let всегда создает новое расположение. Это различие обычно не имеет значения и обнаруживается только в случае переменных, которые переживают свой область видимости с помощью замыканий. Синтаксис let принимает последовательность присвоений и имен переменных, разделенных запятыми:

julia> x, y, z = -1, -1, -1;

julia> let x = 1, z
           println("x: $x, y: $y") # x is local variable, y the global
           println("z: $z") # errors as z has not been assigned yet but is local
       end
x: 1, y: -1
ERROR: UndefVarError: `z` not defined

Присвоения оцениваются в порядке, при этом каждый правой операнд оценивается в области видимости, прежде чем новая переменная в левой части была введена. Поэтому имеет смысл записать что-то вроде let x = x, так как две переменные x различны и имеют отдельные области памяти. Вот пример, где необходимо поведение let.

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           Fs[i] = ()->i
           global i += 1
       end

julia> Fs[1]()
3

julia> Fs[2]()
3

Здесь мы создаем и сохраняем два замыкания, которые возвращают переменную i. Однако, это всегда та же самая переменная i, поэтому два замыкания ведут себя идентично. Мы можем использовать let для создания новой привязки для i:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           let i = i
               Fs[i] = ()->i
           end
           global i += 1
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

Поскольку конструкция begin не вводит новую область видимости, может быть полезно использовать let с нулевым аргументом, чтобы просто ввести новый блок области видимости без создания новых привязок сразу:

julia> let
           local x = 1
           let
               local x = 2
           end
           x
       end
1

Поскольку let вводит новый блок области видимости, внутренняя локальная переменная x — это другая переменная, чем внешняя локальная переменная x. Этот конкретный пример эквивалентен:

julia> let x = 1
           let x = 2
           end
           x
       end
1

Циклы и выражения-генераторы

В циклах и выражениях-генераторах новые переменные, введенные в области видимости их тела, выделяются заново для каждой итерации цикла, как если бы тело цикла было окружено блоком let, как показано в этом примере:

julia> Fs = Vector{Any}(undef, 2);

julia> for j = 1:2
           Fs[j] = ()->j
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

Переменная цикла for или выражения-генератора всегда является новой переменной:

julia> function f()
           i = 0
           for i = 1:3
               # empty
           end
           return i
       end;

julia> f()
0

Однако иногда бывает полезно повторно использовать существующую локальную переменную в качестве переменной цикла. Это можно удобно сделать, добавив ключевое слово outer:

julia> function f()
           i = 0
           for outer i = 1:3
               # empty
           end
           return i
       end;

julia> f()
3

Константы

Распространенное использование переменных — присвоение имен конкретным неизменяемым значениям. Такие переменные присваиваются только один раз. Это намерение может быть передано компилятору с помощью ключевого слова const:

julia> const e  = 2.71828182845904523536;

julia> const pi = 3.14159265358979323846;

Несколько переменных могут быть объявлены в одном операторе const:

julia> const a, b = 1, 2
(1, 2)

Объявление const должно использоваться только в глобальной области видимости для глобальных переменных. Компилятору сложно оптимизировать код, включающий глобальные переменные, поскольку их значения (или даже их типы) могут изменяться практически в любой момент. Если глобальная переменная не будет изменяться, добавление объявления const решает эту проблему производительности.

Локальные константы существенно отличаются. Компилятор может автоматически определить, когда локальная переменная является константой, поэтому объявления локальных констант не требуются и на данный момент не поддерживаются.

Специальные присвоения верхнего уровня, такие как те, что выполняются ключевыми словами function и struct, по умолчанию являются константами.

Обратите внимание, что const влияет только на привязку переменной; переменная может быть привязана к изменяемому объекту (например, массиву), и этот объект по-прежнему может быть изменен. Кроме того, когда кто-то пытается присвоить значение переменной, объявленной как константа, возможны следующие сценарии:

  • если новое значение имеет другой тип, чем тип константы, то выбрасывается ошибка:
julia> const x = 1.0
1.0

julia> x = 1
ERROR: invalid redefinition of constant x
  • если новое значение имеет тот же тип, что и константа, то выводится предупреждение:
julia> const y = 1.0
1.0

julia> y = 2.0
WARNING: redefinition of constant y. This may fail, cause incorrect answers, or produce other errors.
2.0
  • если присвоение не приведет к изменению значения переменной, сообщение не выдается:
julia> const z = 100
100

julia> z = 100
100

Последшее правило применяется к неизменяемым объектам, даже если привязка переменной изменится, например:

julia> const s1 = "1"
"1"

julia> s2 = "1"
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x00000000132c9638
 Ptr{UInt8} @0x0000000013dd3d18

julia> s1 = s2
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x0000000013dd3d18
 Ptr{UInt8} @0x0000000013dd3d18

Однако для изменяемых объектов предупреждение выводится, как ожидалось:

julia> const a = [1]
1-element Vector{Int64}:
 1

julia> a = [1]
WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors.
1-element Vector{Int64}:
 1

Обратите внимание, что хотя иногда это возможно, изменение значения переменной const категорически не рекомендуется и предназначено только для удобства при интерактивном использовании. Изменение констант может привести к различным проблемам или неожиданному поведению. Например, если метод ссылается на константу и уже скомпилирован до изменения константы, то он может продолжать использовать старое значение:

julia> const x = 1
1

julia> f() = x
f (generic function with 1 method)

julia> f()
1

julia> x = 2
WARNING: redefinition of constant x. This may fail, cause incorrect answers, or produce other errors.
2

julia> f()
1

Типизированные глобальные переменные

Поддержка типизированных глобальных переменных была добавлена в Julia 1.8

Аналогично объявлению как констант, глобальные привязки также могут быть объявлены как имеющие постоянный тип. Это можно сделать, не присваивая фактического значения, используя синтаксис global x::T или при присвоении как x::T = 123.

julia> x::Float64 = 2.718
2.718

julia> f() = x
f (generic function with 1 method)

julia> Base.return_types(f)
1-element Vector{Any}:
 Float64

Для любого присвоения глобальной переменной Julia сначала попытается преобразовать ее в соответствующий тип, используя convert:

julia> global y::Int

julia> y = 1.0
1.0

julia> y
1

julia> y = 3.14
ERROR: InexactError: Int64(3.14)
Stacktrace:
[...]

Тип не обязательно должен быть конкретным, но аннотации с абстрактными типами обычно имеют небольшую выгоду в плане производительности.

После присвоения значения или установки типа глобальной переменной, тип привязки больше не может быть изменен:

julia> x = 1
1

julia> global x::Int
ERROR: cannot set type for global x. It already has a value or is already set to a different type.
Stacktrace:
[...]

© 2009–2023 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.9/manual/variables-and-scoping/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API