julia1.0.x での for ループと(グローバル) 変数の使い方。
julia が 1.0 になったとたんに、for ループ内の変数の扱い様式に変更が加えられました。julia 0.6 以前のプログラムが動かない結構な理由になっていると考えます。簡単に言うと、"global" という宣言を付けるべき所に付けるということで、かなり解決しそうです。自分では、漠然と関数内部の変数の扱いに似ているとは思っていましたが、for ループが動く時と、動かない時の区別を理解できていませんでした。
もう少し詳しく知るには、グローバル変数とローカル変数について勉強するのが良いようです。
今さらながら、@yatra9 さんの Julia 1.0の注意点 を良く読んでみたら理解できました。変数のスコープ Scope of Variables という事項の勉強が必要だったようです。将来的には、REPL 内では、特赦の対象となる可能性がある項目のようです。for ループ内で動くか動かないかには、グローバル変数と、ローカル変数というものの理解が必要なようです。ちなみに、julia のコードの高速化のほぼ筆頭に挙げられるのが、グローバル変数を避けるということです。(Juliaのコードを更に高速化する方法) 変数のスコープ Scope of Variables は、それほど英語に精通していなくても、例の部分は理解できるが多いと考えますので、目を通すと理解が深まります。また、@yatra9 さんの Julia 1.0の注意点 には、なんと6項目にまとめていただいています。ここでは、もう少し、手前から説明をしてみます。さらに、for ループと関数化の速度比較も行ってみます。
さて、グローバル変数は、julia を落すか、書き替えるまで、ずっと値を保持します。ローカル変数は、for ... end の中や、function ... end の中だけで働く変数です。普通に、"a=0" と REPL に入力すると、a は 0 という値を、書き替えられない限りずっと格納しています。つまり、a はグローバル変数として扱われています。a = "hoge" としたら、a はグローバル変数で、"hoge" という値を格納しています。このグローバル変数 a をそのまま for ループ内で使用することは可能です。ただし、a を書きかえることはできません。
julia> a = "hoge" "hoge" julia> for i in 1:3 println(a) end hoge hoge hoge
書き替えることはできませんが、a を演算に使うことも可能です。また、グローバル変数 A (::Array) を "for i in A ..." のような書き方をすることも可能です。
julia> a = "hoge" "hoge" julia> for i in 1:3 println(a * string(i)) end hoge1 hoge2 hoge3 julia> A = ["hoge"*string(i) for i in 1:3] 3-element Array{String,1}: "hoge1" "hoge2" "hoge3" julia> for i in A println(i) end hoge1 hoge2 hoge3
では、「グローバル変数を、for ループ内で書き替えることができない」とは、どういうことか? と、いう例を示します。
julia> a = 0 0 julia> for i in 1:3 a = a + 1 end ERROR: UndefVarError: a not defined Stacktrace: [1] top-level scope at ./REPL[123]:2 [inlined] [2] top-level scope at ./none:0
エラー が出ました。for ループの中で、a が global 変数であることを宣言すると、書き替えてくれます。
julia> a = 0 0 julia> for i in 1:3 global a = a + 1 end julia> a 3
このように global という宣言が必要になっています。ちょっとした for ループ内でも必要なのは、ちょっと面倒です。for ループ内での global 変数の宣言を理解できれば、julia 0.6 以前のコードの移植をする元気がだいぶ出てきます。
let ... end でも通りますが、let ... end の間の変数がローカル変数であることに配慮が必要です。
julia> a=0 0 julia> let a = 0 for i in 1:10^5 a = a + 1 end a end 100000 julia> a 0
上の例のように let で計算した a は、グローバル変数の a を書き替えていません。
さて、julia といえば、速度が問題になることも少なくありません。自分の理解では、変数を替えるものは関数化して、1回だけ計算なら for ループで通れば良いと考えていました。for ループで書いても、こうも変数がグローバルか、ローカルかを考えないといけないなら、function ... end との距離は、それ程、違わないように感じます。for ループ にするか、関数化するかは、あとはパフォーマンス次第ということかと考えます。関数化した場合は、コンパイル後の 2回目以降の計算は、julia では圧倒的に速いです。
関数化と for ループ と、どちらが速いか、1例を示します。0 に 1 を 10 万回足す計算をしてみます。以下の2つの式の速度を測定してみましょう。
# for ループ a = 0 @time for i in 1:10^5 global a = a+1 end # 関数化 function add_1() a=0 for i in 1:10^5 a=a+1 end return a end
では、まず for ループから測定してみます。
julia> a=0 0 julia> @time for i in 1:10^5 global a = a+1 end 0.005627 seconds (99.49 k allocations: 1.518 MiB)
続いて、関数化した場合です。1回目はコンパイルの為に、時間が掛りますので、2回、計算してみます。
julia> function add_1() a=0 for i in 1:10^5 a=a+1 end return a end add_1 (generic function with 1 method) julia> @time add_1() 0.012268 seconds (45.12 k allocations: 2.549 MiB) 100000 julia> @time add_1() 0.000002 seconds (5 allocations: 176 bytes) 100000
いったい、2回目以降の関数は何倍速いかは、計算機が必要です。
julia> 0.005627/0.000002 2813.5
なんと2回目以降では、単純な for ループと比較して 2000倍以上、高速化するようです。
1回目は for ループも優秀なので、ちょっと悩むところですね。
おしまい。