Область видимости переменных
Область видимости переменной — это область кода, в которой переменная видна. Область видимости переменных помогает избежать конфликтов имён переменных. Концепция интуитивна: две функции могут иметь аргументы с именем x, не ссылаясь при этом на одну и ту же переменную. Аналогично, существуют и другие случаи, когда разные блоки кода могут использовать одно и то же имя, не ссылаясь на одну и ту же переменную. Правила, определяющие, ссылаются ли одинаковые имена переменных на одну и ту же переменную или нет, называются правилами области видимости; эта секция подробно их описывает.
Некоторые конструкции языка вводят блоки области видимости, которые являются областями кода, подходящими для областей видимости некоторых наборов переменных. Область видимости переменной не может быть произвольным набором строк исходного кода; вместо этого она всегда будет соответствовать одному из этих блоков. Существуют два основных типа областей видимости в Julia: глобальная область видимости и локальная область видимости. Последняя может быть вложенной. В Julia также есть различие между конструкциями, которые вводят «жёсткую область видимости», и теми, которые вводят только «мягкую область видимости», что влияет на то, разрешено ли затенение глобальной переменной переменной с тем же именем.
Конструкции области видимости
Конструкции, вводящие блоки области видимости, следующие:
| Конструкции | Тип области видимости | Разрешено внутри |
|---|---|---|
module, baremodule
|
глобальная | глобальная |
struct |
локальная (мягкая) | глобальная |
for, while, try
|
локальная (мягкая) | глобальная, локальная |
macro |
локальная (жёсткая) | глобальная |
функции, блоки do, блоки let, выражения со списками, генераторы |
локальная (жёсткая) | глобальная, локальная |
Из этой таблицы заметно отсутствие блоков 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
julia> module E
import ..A # make module A available
A.a = 2 # throws below error
end;
ERROR: cannot assign variables in other modules
Обратите внимание, что интерактивный приглашение (также известный как REPL) находится в глобальной области видимости модуля Main.
Локальная область видимости
Новая локальная область видимости вводится большинством блоков кода (см. выше таблицу для полного списка). Если такой блок синтаксически вложен внутри другой локальной области видимости, создаваемая им область видимости вложена во все локальные области видимости, внутри которых она появляется, и все они в конечном итоге вложены в глобальную область видимости модуля, в котором код оценивается. Переменные во внешних областях видимости видны из любой содержащей их области видимости — это означает, что их можно читать и писать во внутренних областях видимости — если только нет локальной переменной с тем же именем, которая «затеняет» внешнюю переменную с тем же именем. Это верно даже в том случае, если внешняя локальная переменная объявлена после (в текстовом смысле ниже) внутреннего блока. Когда мы говорим, что переменная «существует» в заданной области видимости, это означает, что переменная с таким именем существует в любой из областей видимости, в которых текущая область видимости вложена, включая текущую.
Некоторые языки программирования требуют явного объявления новых переменных перед их использованием. Явное объявление работает и в Julia: в любой локальной области видимости запись local x объявляет новую локальную переменную в этой области видимости, независимо от того, существует ли уже переменная с именем x во внешней области видимости или нет. Объявление каждой новой переменной таким образом несколько громоздко и утомительно, поэтому Julia, как и многие другие языки, рассматривает присвоение имени переменной, которая ещё не существует, как неявное объявление этой переменной. Если текущая область видимости является глобальной, новая переменная является глобальной; если текущая область видимости является локальной, новая переменная является локальной для самой внутренней локальной области видимости и будет видна внутри этой области видимости, но не вне её. Если вы присваиваете значение существующей локальной переменной, то это всегда обновляет эту существующую локальную переменную: вы можете затенить локальную переменную только, явно объявив новую локальную переменную во вложенной области видимости с ключевым словом local. В частности, это относится к переменным, присвоенным во вложенных функциях, что может удивить пользователей, пришедших из Python, где присвоение во вложенной функции создаёт новую локальную переменную, если только переменная не объявлена явно как нелокальная.
В основном это довольно интуитивно, но, как и во многих вещах, которые интуитивно понятны, детали более тонкие, чем можно наивно представить.
Когда x = <value> встречается в локальной области видимости, Julia применяет следующие правила, чтобы определить, что означает выражение, исходя из того, где происходит выражение присваивания, и к чему x уже относится в этом месте:
-
Существующая локальная переменная: Если
xуже является локальной переменной, то существующая локальная переменнаяxприсваивается; -
Жёсткая область видимости: Если
xне является уже локальной переменной и присваивание происходит внутри любой конструкции жёсткой области видимости (т. е. внутри тела блокаlet, функции или макроса, выражения со списками или генератора), создаётся новая локальная переменная с именемxв области видимости присваивания; -
Мягкая область видимости: Если
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)
Этот пример иллюстрирует несколько ключевых моментов:
Внутренние области видимости функций — это как любая другая вложенная локальная область видимости. В частности, если переменная уже является локальной вне внутренней функции, и вы присваиваете ей значение внутри внутренней функции, внешняя локальная переменная обновляется.
Не имеет значения, происходит ли определение внешней локальной переменной ниже, чем её обновление; правило остаётся тем же. Весь охватывающий локальный объём анализируется, и его локальные переменные определяются до того, как будут разрешены внутренние локальные значения.
Этот дизайн означает, что вы обычно можете перемещать код в или из внутренней функции без изменения его смысла, что облегчает использование ряда распространённых в языке идиом с использованием замыканий (см. блоки 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 во второй строке цикла локальной, но это невозможно, поскольку две строки находятся в одном блоке области видимости, и каждая переменная может означать только одно в данной области видимости.
О мягкой области видимости
Мы теперь рассмотрели все правила локальной области видимости, но прежде чем завершить эту секцию, возможно, стоит сказать несколько слов о том, почему неоднозначный случай мягкой области видимости обрабатывается по-разному в интерактивном и неинтерактивном контекстах. Возникают два очевидных вопроса:
- Почему это не работает так же, как в REPL повсюду?
- Почему это не работает так же, как в файлах повсюду? И, может быть, пропустить предупреждение?
В Julia ≤ 0.6 все глобальные области видимости работали как в текущем REPL: когда x = <value> появлялось в цикле (или try/catch, или теле struct) , но вне тела функции (или блока let или понимания), определялось, будет ли x локальной для цикла, основываясь на том, определена ли глобальная переменная с именем x или нет. Это поведение имеет преимущество интуитивности и удобства, так как оно максимально приближает поведение внутри тела функции к поведению в REPL. В частности, это упрощает перемещение кода между телом функции и 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 для этого вида кода верхнего уровня:
Уже неудобно копировать и вставлять код из тела функции в REPL для отладки — нужно добавлять аннотации
global, а затем удалять их снова, чтобы вернуться;Начинающие программисты напишут такой код без
globalи не будут знать, почему их код не работает — получаемая ошибка, чтоsне определена, что, похоже, не проясняет никому, кто делает эту ошибку.
Начиная с Julia 1.5, этот код работает без аннотации global в интерактивных контекстах, таких как REPL или Jupyter notebooks (как и в Julia 0.6), а также в файлах и других неинтерактивных контекстах, он печатает это очень прямое предупреждение:
Присвоение
sв мягком области является неоднозначным, так как глобальная переменная с таким же именем существует:sбудет обрабатываться как новая локальная переменная. Устраните неоднозначность, используяlocal sдля подавления этого предупреждения илиglobal sдля присвоения существующей глобальной переменной.
Это решает обе проблемы, сохраняя преимущества поведения 1.0 в отношении "программирования в масштабе": глобальные переменные не оказывают странного влияния на смысл кода, находящегося далеко от них; в REPL копирование и вставка для отладки работают, и у начинающих нет проблем; каждый раз, когда кто-то забывает аннотацию global или случайно затеняет существующую глобальную переменную локальной в мягкой области, что было бы в любом случае запутанно, он получает хорошее ясное предупреждение.
Важное свойство этого дизайна заключается в том, что любой код, выполняющийся в файле без предупреждения, будет вести себя так же в свежем REPL. И наоборот, если вы сохраните сеанс REPL в файл, если он будет вести себя иначе, чем в REPL, вы получите предупреждение.
Блоки Let
let операторы создают новый блок жёсткой области видимости (см. выше) и вводят новые привязки переменных каждый раз, когда они выполняются. В то время как присвоения могут перезаписывать новое значение в существующее местоположение значения, 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
© 2009–2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.7.0/manual/variables-and-scoping/