Область видимости переменных
Область видимости переменной — это область кода, в которой переменная видна. Область видимости переменных помогает избежать конфликтов имён переменных. Концепция интуитивна: две функции могут иметь аргументы, называемые x, без того, чтобы эти два x ссылались на одну и ту же сущность. Аналогично, существуют многие другие случаи, когда разные блоки кода могут использовать одно и то же имя, не ссылаясь на одну и ту же сущность. Правила, определяющие, когда одно и то же имя переменной относится к одной и той же сущности, а когда нет, называются правилами области видимости; в этом разделе они подробно изложены.
Определённые конструкции языка вводят блоки области видимости, которые являются областями кода, которые могут быть областью видимости набора переменных. Область видимости переменной не может быть произвольным набором строк исходного кода; вместо этого она всегда будет соответствовать одному из этих блоков. Существуют два основных типа областей видимости в Julia: глобальная область видимости и локальная область видимости, последняя может быть вложенной. Конструкции, вводящие блоки области видимости, следующие:
| Имя области видимости | Блок/конструкция, вводящая данный вид области видимости | |
|---|---|---|
| глобальная | модуль, baremodule, в интерактивном режиме (REPL) | |
| локальная | мягкая | for, while, comprehension, try-catch-finally, let |
| жёсткая | функции (любой синтаксис, анонимные и do-блоки), тип, неизменяемый, макрос | |
В этой таблице отсутствуют блоки begin и блоки if, которые не вводят новые блоки области видимости. Все три типа областей видимости следуют несколько разным правилам, которые будут объяснены ниже, а также некоторые дополнительные правила для определённых блоков.
Julia использует лексическую область видимости, что означает, что область видимости функции не наследуется от области видимости вызывающей функции, а от области видимости, в которой была определена функция. Например, в следующем коде x внутри foo относится к x в глобальной области видимости своего модуля Bar:
module Bar x = 1 foo() = x end
а не к x в области видимости, где используется foo:
julia> import Bar julia> x = -1; julia> Bar.foo() 1
Таким образом, лексическая область видимости означает, что область видимости переменных может быть определена по одному лишь исходному коду.
Глобальная область видимости
Каждый модуль вводит новую глобальную область видимости, отделённую от глобальной области видимости всех других модулей; не существует всеохватывающей глобальной области видимости. Модули могут ввести переменные других модулей в свою область видимости через инструкции using или import или через квалифицированный доступ с помощью нотации точки, т. е. каждый модуль является так называемым пространством имён. Обратите внимание, что привязки переменных могут быть изменены только внутри их глобальной области видимости, а не из внешнего модуля.
module A
a = 1 # a global in A's scope
end
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: cannot assign variables in other modules"
end
Обратите внимание, что интерактивный интерфейс (также известный как REPL) находится в глобальной области видимости модуля Main.
Локальная область видимости
Новая локальная область видимости вводится большинством блоков кода, см. вышеприведённую таблицу для полного списка. Локальная область видимости обычно наследует все переменные из родительской области видимости, как для чтения, так и для записи. Существуют два подтипа локальных областей видимости — жёсткая и мягкая — с немного отличающимися правилами относительно того, какие переменные наследуются. В отличие от глобальных областей видимости, локальные области видимости не являются пространствами имён, поэтому переменные во внутренней области видимости не могут быть получены из родительской области видимости каким-либо способом квалифицированного доступа.
Следующие правила и примеры относятся как к жёстким, так и к мягким локальным областям видимости. Новая переменная, введённая в локальной области видимости, не распространяется обратно в родительскую область видимости. Например, здесь z не вводится в область видимости верхнего уровня:
for i=1:10
z = i
end
julia> z
ERROR: UndefVarError: z not defined
(Обратите внимание, в этом и всех последующих примерах предполагается, что их верхний уровень — это глобальная область видимости с чистой рабочей областью, например, только что запущенный REPL.)
Внутри локальной области видимости переменную можно принудительно сделать локальной, используя ключевое слово local:
x = 0
for i=1:10
local x
x = i + 1
end
julia> x
0
Внутри локальной области видимости можно определить новую глобальную переменную, используя ключевое слово global:
for i=1:10
global z
z = i
end
julia> z
10
Расположение как ключевого слова local, так и global внутри блока области видимости не имеет значения. Следующее эквивалентно последнему примеру (хотя стилистически хуже):
for i=1:10
z = i
global z
end
julia> z
10
Несколько определений глобальных или локальных переменных могут находиться в одной строке и также могут быть соединены с присвоениями:
for i=1:10
global x=i, y, z
local a=4, b , c=1
end
Мягкая локальная область видимости
В мягкой локальной области видимости все переменные наследуются из родительской области видимости, если переменная не помечена явно ключевым словомlocal. Мягкие локальные области видимости вводятся циклами for, while, comprehension, блоками try-catch-finally и let. Существуют некоторые дополнительные правила для блоков let и для циклов for и comprehension.
В следующем примере x и y всегда ссылаются на одни и те же переменные, поскольку мягкая локальная область видимости наследует как переменные для чтения, так и для записи:
x,y = 0, 1
for i = 1:10
x = i + y + 1
end
julia> x
12
Внутри мягких областей видимости ключевое слово global никогда не является необходимым, хотя и разрешено. Единственный случай, когда оно изменит семантику, — это (в настоящее время) синтаксическая ошибка:
let
local x = 2
let
global x = 3
end
end
# ERROR: syntax: `global x`: x is local variable in the enclosing scope
Жёсткая локальная область видимости
Жёсткие локальные области видимости вводятся определениями функций (всех их форм), блоками type & immutable и определениями макросов.
В жёсткой локальной области видимости все переменные наследуются из родительской области видимости, за исключением следующих случаев:
- присвоение приведет к изменению глобальной переменной, или
- переменная явно помечена ключевым словом
local.
Таким образом, глобальные переменные наследуются только для чтения, но не для записи:
x,y = 1,2
function foo()
x = 2 # assignment introduces a new local
return x + y # y refers to the global
end
julia> foo()
4
julia> x
1
Для присвоения глобальной переменной требуется явное использование global:
x = 1
function foo()
global x = 2
end
foo()
julia> x
2
Обратите внимание, что вложенные функции могут вести себя по-разному по сравнению с функциями, определёнными в глобальной области видимости, так как они могут изменять локальные переменные родительской области видимости:
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)
Различие в наследовании глобальных и локальных переменных при присвоении может привести к некоторым небольшим различиям между функциями, определёнными в локальных и глобальных областях видимости. Рассмотрим модификацию последнего примера путём перемещения bar в глобальную область видимости:
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)
Обратите внимание, что вышеупомянутый нюанс не относится к определениям типов и макросов, так как они могут появляться только в глобальной области видимости. Существуют специальные правила области видимости, касающиеся оценки аргументов функций по умолчанию и ключевых аргументов, которые описаны в разделе про функции.
Присвоение переменной, используемой внутри определения функции, типа или макроса, необязательно должно предшествовать её внутреннему использованию:
julia> f = y -> x + y (::#1) (generic function with 1 method) julia> f(3) ERROR: UndefVarError: x not defined in (::##1#2)(::Int64) at ./none:1 ... julia> x = 1 1 julia> f(3) 4
Это поведение может показаться немного необычным для обычной переменной, но позволяет использовать именованные функции — которые являются просто обычными переменными, содержащими объекты функций, — до их определения. Это позволяет определять функции в любом интуитивно понятном и удобном порядке, а не принуждая к упорядочению снизу вверх или не требуя предварительных объявлений, при условии, что они определены к моменту фактического вызова. В качестве примера здесь представлен неэффективный, взаимно рекурсивный способ проверки, являются ли положительные целые числа чётными или нечётными:
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 предоставляет встроенные, эффективные функции проверки чётности и нечётности, называемые iseven() и isodd(), поэтому вышеприведённые определения следует рассматривать только как примеры.
Жёсткая vs. мягкая локальная область видимости
Блоки, которые вводят мягкую локальную область видимости, такие как циклы, обычно используются для манипулирования переменными в родительской области видимости. Таким образом, их значением по умолчанию является полный доступ ко всем переменным в родительской области видимости.
Напротив, код внутри блоков, которые вводят жёсткую локальную область видимости (определения функций, типов и макросов), может быть выполнен в любом месте программы. Дистанционное изменение состояния глобальных переменных в других модулях следует выполнять с осторожностью, и поэтому это опциональная возможность, требующая ключевого слова global.
Причина, по которой допускается изменение локальных переменных родительских областей видимости во вложенных функциях, заключается в возможности создания замыканий, которые имеют закрытое состояние, например, переменная state в следующем примере:
let
state = 0
global counter
counter() = state += 1
end
julia> counter()
1
julia> counter()
2
См. также замыкания в примерах в следующих двух разделах.
Блоки let
В отличие от присвоений локальным переменным, операторы let каждый раз при выполнении выделяют новые привязки переменных. Присвоение изменяет существующее расположение значения, а let создаёт новые расположения. Это различие обычно не имеет значения и заметно только в случае переменных, которые переживают свою область видимости через замыкания. Синтаксис let принимает серию присвоений и имён переменных, разделённых запятыми:
let var1 = value1, var2, var3 = value3
code
end
Присвоения вычисляются в порядке следования, причём правые части вычисляются в области видимости перед тем, как новая переменная в левой части будет введена. Поэтому имеет смысл написать что-то вроде let x = x, так как две переменные x различаются и имеют отдельные области хранения. Вот пример, где необходимо поведение let:
Fs = Array{Any}(2)
i = 1
while i <= 2
Fs[i] = ()->i
i += 1
end
julia> Fs[1]()
3
julia> Fs[2]()
3
Здесь мы создаём и сохраняем два замыкания, которые возвращают переменную i. Однако, это всегда одна и та же переменная i, поэтому два замыкания ведут себя одинаково. Мы можем использовать let для создания новой привязки для i:
Fs = Array{Any}(2)
i = 1
while i <= 2
let i = i
Fs[i] = ()->i
end
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.
Циклы for и списки
for циклы и списки ведут себя следующим образом: любые новые переменные, введённые в их областях видимости, выделены заново для каждой итерации цикла. Это в отличие от while циклов, которые повторно используют переменные для всех итераций. Таким образом, эти конструкции похожи на while циклы с let блоками внутри:
Fs = Array{Any}(2)
for i = 1:2
Fs[i] = ()->i
end
julia> Fs[1]()
1
julia> Fs[2]()
2
for циклы будут повторно использовать существующие переменные для переменной итерации:
i = 0 for i = 1:3 end i # here equal to 3
Однако списки не делают этого и всегда выделяют свои переменные итерации заново:
x = 0 [ x for x=1:3 ] x # here still equal to 0
Константы
Распространённое использование переменных — присвоение имён конкретным неизменным значениям. Такие переменные присваиваются только один раз. Это намерение может быть передано компилятору с помощью ключевого слова const:
const e = 2.71828182845904523536 const pi = 3.14159265358979323846
Декларация const разрешена для глобальных и локальных переменных, но особенно полезна для глобальных. Компилятору сложно оптимизировать код, включающий глобальные переменные, поскольку их значения (или даже типы) могут меняться практически в любое время. Если глобальная переменная не будет изменяться, добавление декларации const решает эту проблему производительности.
Локальные константы сильно отличаются. Компилятор может автоматически определить, когда локальная переменная является константой, поэтому объявления локальных констант не нужны для повышения производительности.
Специальные задания на верхнем уровне, такие как те, которые выполняются ключевыми словами function и type , по умолчанию являются константами.
Обратите внимание, что const влияет только на привязку переменной; переменная может быть связана с изменяемым объектом (таким как массив), и этот объект всё ещё может быть изменён.
© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.5/manual/variables-and-scoping/