Juliaの変数スコープについて

(2020年8月15日:Julia 1.5で、REPLではlocalスコープ内からglobal変数を参照できるようになりました。Julia 0.6以前の挙動に戻ったことになります)

Juliaの変数スコープMatlabC言語と少し違うので混乱した話。(2019年8月25日:global/localスコープの説明が公式ドキュメントと違ったので修正しました)

たとえば、1から10までの整数の和を求めたいとき、C言語では以下のようにやるかと思います。

#include <stdio.h>

int main(int argc, char *argv[]) {
  int total = 0;
  int x;

  for (x=1; x<=10; x++) {
    total += x;
  }

  printf("Total: %d\n", total);

  return(0);
}

totalfor文の内側から更新することができます。

あるいはPython 3のREPLでも以下のようになります。forの内側から、外側にあるtotalにアクセスできていることが分かります。

>>> total = 0
>>> for x in range(1, 11):
...   total += x
... 
>>> total
55

同じことをJulia 1.1でやってみます。ここでは(立ち上げたばかりのREPLなど)トップレベルでの実行を想定しています。

julia> total = 0;

julia> for x = 1:10
         total += x;
       end
ERROR: UndefVarError: total not defined
Stacktrace:
 [1] top-level scope at ./REPL[2]:2 [inlined]
 [2] top-level scope at ./none:0

公式ドキュメントによると、for文は新しいlocalスコープを作るため、内部(localスコープ)から外側(globalスコープ)の変数を見ることができないとのことです。回避するにはglobalを使うらしく、

total = 0;
for x = 1:10
  global total += x;
end
println("Total: $(total)")

とすれば期待通りの動作になります。globalというキーワードを付けてあげれば上位にあるglobalスコープの変数にもアクセスできるのだな、ふむふむ、という感じです。

ただ、問題はここからで、二重のループにするとそういったことが起こりません。for yの中から、その外側にあるはずのsubtotalを更新できます。

for x = 1:10
  subtotal = 0;
  for y = 1:10
    subtotal += y;
  end
end

これは(Local Scopesに書いてありますが)、localスコープ内に作られたlocalスコープからは上位の変数にアクセスできるからなのだそうです。外側のforがlocalスコープを作ったので、その内側のfor文内部から見ると上位がlocalスコープなので、for yからfor x内の変数にアクセスできるのです。

同じものを関数にした場合はどうなるでしょうか。

function testsum()
  total = 0;
  for x = 1:10
    total += x;
  end
  return(total);
end

この場合は、functionが新しいlocalスコープを作るので、その内側に作られたlocalスコープのforから見て上位にあるtotalにはアクセスできます。

globalはキモい(し、不具合の温床になる)のでできるだけ使わない方がよさそう、と考えると、Julia言語としては「できるだけ関数にしよう」という方向にユーザーを誘導していますのでるのではないかと感じます。そういえば、関数化した方が実行も速くなることが多いです。(追記:公式スタイルガイドに「スクリプトではなく関数にせよ」と書いてありました)

同じような操作であっても言語ごとに少しずつ違うので、細かいところで「どうなっていたっけ?」とドキュメントを参照する必要がありますが、こういうところから言語設計者の考え方に思いをはせるのもプログラミングの楽しいところです。