Здесь есть следующие взаимосвязанные проблемы:
- Значения в Julia могут быть изменяемыми или неизменными.
- Переменная в Julia связана со значением (которое может быть неизменным или изменяемым).
- Некоторые операции могут изменять изменяемое значение.
Таким образом, первый пункт касается изменчивости по сравнению с неизменяемостью значений. Обсуждение в руководстве Юлии дано здесь . Вы можете проверить, является ли значение изменчивым или нет, используя функцию isimmutable
.
Типичные случаи:
- числа, строки,
Tuple
, NamedTuple
, struct
s неизменны
julia> isimmutable(1)
true
julia> isimmutable("sdaf")
false
julia> isimmutable((1,2,3))
true
Массивы, dicts,
mutable structs
et c. (в общем случае типы контейнеров, отличные от
Tuple
,
NamedTuple
и
struct
s), являются изменяемыми:
julia> isimmutable([1,2,3])
false
julia> isimmutable(Dict(1=>2))
false
Основное различие между неизменяемыми и изменяемыми значениями заключается в том, что изменяемые значения могут иметь свои Содержание изменено. Вот простой пример:
julia> x = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> x[1] = 10
10
julia> x
3-element Array{Int64,1}:
10
2
3
Теперь давайте разберем то, что мы видели здесь:
- оператор присваивания
x = [1, 2, 3]
связывает значение (в данном случае вектор) в переменную x
- оператор
x[1] = 10
изменяет значение (вектор) на месте
Обратите внимание, что то же самое не получится для Tuple
, поскольку он неизменен :
julia> x = (1,2,3)
(1, 2, 3)
julia> x[1] = 10
ERROR: MethodError: no method matching setindex!(::Tuple{Int64,Int64,Int64}, ::Int64, ::Int64)
Теперь мы подошли ко второму пункту - привязке значения к имени переменной. Обычно это делается с помощью оператора =
, если с левой стороны мы видим имя переменной, как указано выше, с x = [1,2,3]
или x = (1,2,3)
.
Обратите внимание, что, в частности, также +=
(и аналогичные) выполняем повторное связывание, например:
julia> x = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> y = x
3-element Array{Int64,1}:
1
2
3
julia> x += [1,2,3]
3-element Array{Int64,1}:
2
4
6
julia> x
3-element Array{Int64,1}:
2
4
6
julia> y
3-element Array{Int64,1}:
1
2
3
, так как в этом случае это просто сокращение x = x + [1, 2, 3]
, и мы знаем, что =
повторное связывание.
В частности (как @pszufe в комментарии) если вы передаете значение функции, то ничего не копируется. Здесь происходит то, что переменная, которая находится в сигнатуре функции, связана с переданным значением (этот тип поведения иногда называется pass by share ). Итак, у вас есть:
julia> x = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> f(y) = y
f (generic function with 1 method)
julia> f(x) === x
true
По сути, то, что происходит, «как если бы» вы написали y = x
. Разница в том, что функция создает переменную y
в новой области (область действия функции), тогда как y = x
создает привязку значения, с которым x
связана с переменной y
в области действия, где оператор y = x
присутствует.
Теперь, с другой стороны, такие вещи, как x[1] = 10
(по сути, приложение функции setindex!
) или x .= [1,2,3]
, являются операциями на месте (они не перепривязывают значение но попробуйте изменить контейнер). Так что это работает на месте (обратите внимание, что в примере я объединяю вещание с +=
, чтобы сделать его на месте):
julia> x = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> y = x
3-element Array{Int64,1}:
1
2
3
julia> x .+= [1,2,3]
3-element Array{Int64,1}:
2
4
6
julia> y
3-element Array{Int64,1}:
2
4
6
, но если мы попытались сделать то же самое с, например,. целое число, которое является неизменным, операция завершится ошибкой:
julia> x = 10
10
julia> x .+= 1
ERROR: MethodError: no method matching copyto!(::Int64, ::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{0},Tuple{},typeof(+),Tuple{Int64,Int64}})
То же самое с установкой индекса для неизменяемого значения:
julia> x = 10
10
julia> x[] = 1
ERROR: MethodError: no method matching setindex!(::Int64, ::Int64)
Наконец, третье - какие операции пытаются изменить значение на месте. Мы уже отметили некоторые из них (например, setindex!
: x[10] = 10
и широковещательное задание x .= [1,2,3]
). В общем, не всегда легко определить, будет ли вызов f(x)
видоизменяться x
, если f
является некоторой общей функцией (он может или не может видоизменять x
, если x
является изменяемым). Поэтому в Джулии существует соглашение о добавлении !
в конце имен функций, которые могут изменять свои аргументы, чтобы визуально сигнализировать об этом (следует подчеркнуть, что это только соглашение - в частности, просто добавление !
в конце имя функции не имеет прямого влияния на то, как она работает). Мы уже видели это с setindex!
(для которого сокращение x[1] = 10
, как обсуждалось), но вот другой пример:
julia> x = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> filter(==(1), x) # no ! so a new vector is created
1-element Array{Int64,1}:
1
julia> x
3-element Array{Int64,1}:
1
2
3
julia> filter!(==(1), x) # ! so x is mutated in place
1-element Array{Int64,1}:
1
julia> x
1-element Array{Int64,1}:
1
Если вы используете функцию (например, setindex!
), которая мутирует свой аргумент и хочет избежать мутации, используя copy
при передаче ему аргумента (или deepcopy
, если ваша структура многократно вложена и потенциально мутация может произойти на более глубоком уровне - но это редко).
Так в нашем примере:
julia> x = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> y = filter!(==(1), copy(x))
1-element Array{Int64,1}:
1
julia> y
1-element Array{Int64,1}:
1
julia> x
3-element Array{Int64,1}:
1
2
3