Область видимости переменных
Область видимости переменной — это область кода, в которой переменная видна. Область видимости переменных помогает избежать конфликтов именования переменных. Концепция интуитивно понятна: две функции могут иметь аргументы, называемые x без того, чтобы оба 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, как и многие другие языки, считает присвоение новой переменной в локальной области видимости неявным объявлением этой переменной в качестве новой локальной переменной. В основном это довольно интуитивно понятно, но, как и многие вещи, которые интуитивно понятны, детали более тонкие, чем можно было бы наивно предположить.
Когда 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, это не имело бы значения — правило жёсткого пространства имен не зависит от чего-либо в глобальном пространстве имен.
Перейдём к некоторым более неоднозначным случаям, охватываемым правилом мягкого пространства имен. Мы исследуем это, извлекая тела функций greet и sum_to_def в контексты мягкого пространства имен. Сначала поместим тело функции greet в цикл for — который является мягким, а не жёстким — и оценим его в интерактивной оболочке:
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)
Что делает этот код? Подсказка: это вопрос-ловушка. Ответ — "зависит". Если этот код вводится интерактивно, он ведёт себя так же, как и внутри тела функции. Но если код находится в файле, он выводит предупреждение об неоднозначности и генерирует ошибку неопределённой переменной. Давайте сначала посмотрим, как он работает в интерактивной оболочке:
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
Интерактивная оболочка приближает поведение к поведению внутри тела функции, определяя, присваивает ли присваивание внутри цикла глобальной или создаёт новую локальную переменную в зависимости от того, определена ли глобальная переменная с таким именем или нет. Если глобальная переменная с таким именем существует, то присваивание обновляет её. Если глобальная переменная не существует, то присваивание создаёт новую локальную переменную. В этом примере мы видим оба случая в действии:
- Глобальная переменная с именем
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 на этом файле — результат будет таким же. Как вы видите, это поведение существенно отличается от оценки того же кода в интерактивной оболочке. Давайте разберём, что происходит здесь:
- Глобальная переменная
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 на второй строке цикла — локальной, но это невозможно, так как две строки находятся в одном блоке области видимости, и каждая переменная может означать только одно в данной области видимости.
О мягком пространстве имен
Мы теперь рассмотрели все правила локального пространства имен, но прежде чем завершить этот раздел, следует сказать несколько слов о том, почему неоднозначный случай мягкого пространства имен обрабатывается по-разному в интерактивных и неинтерактивных контекстах. Возникают два очевидных вопроса:
- Почему это не работает так же, как в интерактивной оболочке во всех случаях?
- Почему это не работает так же, как в файлах во всех случаях? И, может быть, обойтись без предупреждения?
В Julia ≤ 0.6 все глобальные пространства имён работали так же, как текущая интерактивная оболочка: когда происходило x = <value> в цикле (или try/catch, или в теле struct ), но за пределами тела функции (или let блока или генератора), принималось решение о том, будет ли x локальной для цикла, в зависимости от того, определена ли глобальная переменная с именем x или нет. Это поведение имеет преимущество интуитивности и удобства, так как оно максимально приближает поведение внутри тела функции. В частности, это облегчает перемещение кода между телом функции и интерактивной оболочкой при отладке поведения функции. Однако это имеет и некоторые недостатки. Во-первых, это довольно сложное поведение: многие люди на протяжении многих лет были сбиты с толку этим поведением и жаловались, что оно сложно для объяснения и понимания. Согласен. Во-вторых, и, возможно, ещё хуже, это плохо для программирования в "масштабе". Когда вы видите небольшой фрагмент кода в одном месте, как в этом случае, очень ясно, что происходит:
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 для такого кода верхнего уровня:
Теперь неудобно копировать и вставлять код из тела функции в интерактивную оболочку для отладки — нужно добавлять аннотации
globalи затем снова удалять их, чтобы вернуться;Начинающие программисты напишут этот код без аннотации
globalи не поймут, почему их код не работает — ошибка, которую они получат, будет заключаться в том, чтоsнеопределена, что не проясняет ситуацию для тех, кто допускает эту ошибку.
Начиная с Julia 1.5, этот код работает без аннотации global в интерактивных контекстах, таких как интерактивная оболочка или Jupyter-тетради (как и в Julia 0.6), а в файлах и других неинтерактивных контекстах он выводит это прямое предупреждение:
Присваивание
sв мягком пространстве имен неоднозначно, потому что глобальная переменная с тем же именем существует:sбудет обработана как новая локальная переменная. Устраните неоднозначность, используяlocal sдля подавления этого предупреждения илиglobal sдля присваивания существующей глобальной переменной.
Это решает обе проблемы, сохраняя при этом преимущества "программирования в масштабе" поведения 1.0: глобальные переменные не оказывают никакого "жуткого воздействия" на значение кода, который может находиться далеко; в интерактивной оболочке отладка с помощью копирования-вставки работает, и начинающие программисты не сталкиваются с проблемами; всякий раз, когда кто-то забывает аннотацию global или случайно затеняет существующую глобальную переменную локальной в мягком пространстве имён, что само по себе было бы запутанным, они получают приятное чёткое предупреждение.
Важным свойством этой конструкции является то, что любой код, который выполняется в файле без предупреждений, будет вести себя так же в новой интерактивной оболочке. И наоборот, если вы сохраните сеанс интерактивной оболочки в файл, если он будет вести себя иначе, чем в интерактивной оболочке, вы получите предупреждение.
Блоки 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.
Циклы и генераторы
В циклах и генераторах, новые переменные, введенные в областях видимости их тела, заново выделяются для каждой итерации цикла, как если бы тело цикла было окружено блоком 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.6.0/manual/variables-and-scoping/