Juliaの変数束縛で不思議な気持ちに包まれたので記録しておく。
Juliaは1.2を用いている。
問題の挙動
下記のコードは正常に動く。
x = "hello"
for i in 1:1
println(x)
end
#hello
しかし、下記のコードはエラーを吐く。
x = "hello"
for i in 1:1
println(x)
x = "huga"
end
#ERROR: UndefVarError: x not defined
ちなみに、下記のコードは問題なく動く。
x = "hello"
for i in 1:1
x = "huga"
end
#hello
また、下記のコードも同様に問題なく動く。
let
x = "hello"
for i in 1:1
println(x)
x = "huga"
end
end
#hello
原因
これは、Juliaのグローバルスコープとローカルスコープの挙動の差に起因する。1.2のドキュメントによれば、
https://docs.julialang.org/en/v1.2/manual/variables-and-scoping/#scope-of-variables-1
ローカルスコープからグローバルスコープの変数に対しては、読みだしはすることができるが、書き込みはすることができない。そのため、ローカルスコープ内でグローバルスコープの変数と同名の変数が定義された場合、その変数には新しいメモリ領域が割り当てられ、ローカルスコープ内の別変数とみなされることになる。
この適応範囲が少し紛らわしかったポイントで、juliaでは、ローカルスコープ内のグローバル変数と同名の変数に一つでも書き込み権限を要する操作が加えられると、その名前の変数は全てローカルスコープ内の変数とみなす挙動をする。つまり、ローカルスコープ内を遡って影響が波及する。これは、スコープ内のどこでlocal
キーワードを宣言しても挙動に差がでない、というドキュメントの内容に照らし合わせると素直な挙動ともいえる。
今回の例でいうと、例2では、4行目でxへの代入が行われているため、このローカルスコープ内のxは全てグローバルスコープの変数xとは別の変数と解釈されることになる。すると、println関数は結果としてまだ定義されていないローカルスコープ内のxを見に行こうとするため、x not defined
というエラーが発生してしまう。
一方、例4では、コード全体がlet
で囲われることによって、ローカルスコープが入れ子構造になったプログラムになっている。つまり、はじめにxが定義されている場所はグローバルスコープではなくローカルスコープだ。ローカルスコープが入れ子になっている場合は、グローバルスコープの場合とは違い、内側のローカルスコープは外側のローカルスコープの変数への書き込み権限も同様に持っている。そのため、例4ではxは全て一番初めに定義されたxと同じものを指すことになり、エラーは発生しない。
このことは、一番内側のローカルスコープ内でのxへの変更がその外側のxに伝搬していることかからも確認できる。
let
x = "hello"
for i in 1:1
println(x)
x = "huga"
end
println(x)
end
#hello
#huga
この挙動はjuliaのバージョンが0.6->1.0に変わった際に入った変更らしく、インターネットに転がっている0.6のマニュアルの邦訳
https://hshindo.github.io/julia-doc-ja-v0.6/manual/variables-and-scoping.html
などを見るとより混乱することになるので気をつけたい。