Scope of Variables

変数のスコープは、変数がアクセス可能なコードの領域です。変数のスコーピングは、変数名の衝突を避けるのに役立ちます。この概念は直感的です:2つの関数は、両方ともxという名前の引数を持つことができますが、2つのxが同じものを指すわけではありません。同様に、異なるコードブロックが同じ名前を使用しても、同じものを指さない多くのケースがあります。同じ変数名が同じものを指すかどうかのルールはスコープルールと呼ばれ、このセクションではそれらを詳細に説明します。

言語の特定の構造はスコープブロックを導入します。これは、特定の変数のセットがスコープとして適用されるコードの領域です。変数のスコープは任意のソース行のセットではなく、常にこれらのブロックのいずれかに一致します。Juliaには主に2種類のスコープがあります。グローバルスコープとローカルスコープです。後者はネスト可能です。また、Juliaには「ハードスコープ」を導入する構造と「ソフトスコープ」だけを導入する構造の区別があります。これは、shadowingという同名のグローバル変数が許可されるかどうかに影響します。

Summary

グローバルスコープで定義された変数は、コードが実行される場所に応じて、内部のローカルスコープでは未定義である可能性があります。これは、安全性と便利さのバランスを取るためです。ハードおよびソフトローカルスコーピングルールは、グローバル変数とローカル変数の相互作用を定義します。

ただし、ローカルスコープ内でのみ定義された変数は、すべてのコンテキストで一貫して動作します。変数がすでに定義されている場合、それは再利用されます。変数が定義されていない場合、それは現在のスコープおよび内部スコープで利用可能になります(ただし外部スコープでは利用できません)。

A Common Confusion

予期しない未定義の変数に遭遇した場合、

# Print the numbers 1 through 5
i = 0
while i < 5
    i += 1     # ERROR: UndefVarError: `i` not defined
    println(i)
end

簡単な修正は、すべてのグローバル変数の定義をローカル定義に変更することで、コードを let ブロックまたは function で囲むことです。

# Print the numbers 1 through 5
let i = 0
    while i < 5
        i += 1     # Now outer `i` is defined in the inner scope of the while loop
        println(i)
    end
end

これは手続き型スクリプトを書く際によくある混乱の原因ですが、コードが関数内に移動されるか、REPLで対話的に実行されると問題ではなくなります。

次のキーワード global と local を参照して、明示的に望ましいスコーピング動作を達成してください。

Scope Constructs

スコープブロックを導入する構文は次のとおりです:

ConstructScope Type IntroducedScope Types Able to Contain Construct
module, baremoduleglobalglobal
structlocal (hard)global
macrolocal (hard)global
for, while, trylocal (soft)global, local
function, do, let, comprehensions, generatorslocal (hard)global, local

この表に欠けているのは begin blocks と if blocks で、これらは新しいスコープを導入しません。3種類のスコープは、以下で説明するように、やや異なるルールに従います。

Juliaはlexical scopingを使用します。これは、関数のスコープが呼び出し元のスコープからではなく、関数が定義されたスコープから継承されることを意味します。例えば、以下のコードでは、foo内のxはモジュールBarのグローバルスコープにあるxを参照します:

julia> module Bar
           x = 1
           foo() = x
       end;

foo が使用されているスコープ内に x は存在しません:

julia> import .Bar

julia> x = -1;

julia> Bar.foo()
1

したがって、レキシカルスコープとは、特定のコードの中で変数が何を指しているかを、その変数が出現するコードから推測できることを意味し、プログラムの実行方法には依存しません。別のスコープの中にネストされたスコープは、そのスコープが含まれるすべての外側のスコープ内の変数を「見る」ことができます。一方、外側のスコープは内側のスコープ内の変数を見ることができません。

Global Scope

各モジュールは、他のすべてのモジュールのグローバルスコープとは別の新しいグローバルスコープを導入します—包括的なグローバルスコープは存在しません。モジュールは、using or import ステートメントを通じて、またはドット表記を使用した修飾アクセスを通じて、他のモジュールの変数を自分のスコープに導入することができます。つまり、各モジュールは、名前と値を関連付けるファーストクラスのデータ構造であるいわゆる 名前空間 です。

トップレベルの式に local キーワードを使った変数宣言が含まれている場合、その変数はその式の外部からはアクセスできません。式内の変数は、同じ名前のグローバル変数には影響を与えません。例として、トップレベルで begin または if ブロック内に local x を宣言することが挙げられます:

julia> x = 1
       begin
           local x = 0
           @show x
       end
       @show x;
x = 0
x = 1

モジュール Main のグローバルスコープにインタラクティブプロンプト(別名 REPL)があることに注意してください。

Local Scope

ほとんどのコードブロックによって新しいローカルスコープが導入されます(完全なリストについては上記の table を参照してください)。そのようなブロックが別のローカルスコープの内部に構文的にネストされている場合、作成されるスコープは、その中に現れるすべてのローカルスコープの内部にネストされ、最終的にはコードが評価されるモジュールのグローバルスコープの内部にネストされます。外側のスコープの変数は、それらを含む任意のスコープから可視であり、つまり内部スコープで読み書きが可能です — 同じ名前のローカル変数が外側の同じ名前の変数を「シャドウイング」しない限り。これは、外側のローカルが内部ブロックの後(テキスト的に下に)に宣言されている場合でも当てはまります。変数が特定のスコープに「存在する」と言うとき、これはその名前の変数が現在のスコープがネストされている任意のスコープ、現在のスコープを含む、に存在することを意味します。

いくつかのプログラミング言語では、新しい変数を使用する前に明示的に宣言する必要があります。明示的な宣言はJuliaでも機能します:任意のローカルスコープ内で、local xと書くことで、そのスコープ内に新しいローカル変数が宣言されます。外側のスコープに既にxという名前の変数が存在するかどうかに関わらずです。ただし、このように新しい変数を宣言するのはやや冗長で面倒です。そのため、Juliaは他の多くの言語と同様に、既に存在しない変数名への代入をその変数を暗黙的に宣言するものと見なします。現在のスコープがグローバルであれば、新しい変数はグローバルになります;現在のスコープがローカルであれば、新しい変数は最も内側のローカルスコープにローカルであり、そのスコープ内では可視ですが、外側では可視ではありません。既存のローカルに代入すると、常にその既存のローカルが更新されます:ローカルをシャドウイングするには、localキーワードを使ってネストされたスコープ内で新しいローカルを明示的に宣言する必要があります。特に、これは内部関数で代入された変数に適用されます。これは、内部関数での代入が新しいローカルを作成するPythonから来たユーザーには驚くべきことかもしれません。変数が明示的に非ローカルとして宣言されていない限りです。

ほとんどの場合、これは非常に直感的ですが、直感的に振る舞う多くの事柄と同様に、詳細は単純に想像するよりも微妙です。

x = <value>がローカルスコープで発生すると、Juliaは代入式が発生する場所とその時点でxが何を参照しているかに基づいて、式の意味を決定するために以下のルールを適用します。

  1. 既存のローカル: x が すでにローカル変数 である場合、既存のローカル x に代入されます;

  2. ハードスコープ: x が すでにローカル変数でない 場合、ハードスコープ構造(すなわち、let ブロック、関数、構造体またはマクロの本体、コンプリヘンション、またはジェネレーター)のいずれかの内部で代入が行われると、代入のスコープ内に新しいローカル変数 x が作成されます;

  3. ソフトスコープ: もし x が すでにローカル変数でない 場合、かつ代入を含むすべてのスコープ構造がソフトスコープ(ループ、try/catch ブロック)であるなら、動作はグローバル変数 x が定義されているかどうかに依存します:

    • グローバルな x が 未定義 の場合、代入のスコープ内に新しいローカル x が作成されます;

    • グローバル x が 定義されている 場合、代入は曖昧と見なされます:

      • 非対話的なコンテキスト(ファイル、eval)では、曖昧さの警告が表示され、新しいローカルが作成されます;
      • インタラクティブ コンテキスト (REPL、ノートブック) では、グローバル変数 x が割り当てられます。

非対話的なコンテキストでは、ハードスコープとソフトスコープの動作は同じであることに注意してください。ただし、暗黙的にローカルな変数(つまり、local xで宣言されていない)がグローバルを隠す場合、警告が表示されます。対話的なコンテキストでは、便利さのためにより複雑なヒューリスティックに従います。これは、以下の例で詳しく説明されています。

ルールを理解したので、いくつかの例を見てみましょう。各例は新しいREPLセッションで評価されると仮定されており、そのブロック内で割り当てられたグローバル変数のみが存在します。

明確でシンプルな状況から始めましょう。これは、関数の本体という厳密なスコープ内での代入であり、その名前のローカル変数がすでに存在しない場合です。

julia> function greet()
           x = "hello" # new local
           println(x)
       end
greet (generic function with 1 method)

julia> greet()
hello

julia> x # global
ERROR: UndefVarError: `x` not defined in `Main`

greet 関数の内部では、代入 x = "hello" により x は関数のスコープ内で新しいローカル変数になります。関連する2つの事実があります:代入はローカルスコープ内で行われ、既存のローカル x 変数は存在しません。x がローカルであるため、グローバルに x という名前の変数が存在するかどうかは関係ありません。ここでは、例えば greet を定義して呼び出す前に x = 123 を定義します:

julia> x = 123 # global
123

julia> function greet()
           x = "hello" # new local
           println(x)
       end
greet (generic function with 1 method)

julia> greet()
hello

julia> x # global
123

greet内のxはローカルであるため、greetを呼び出してもグローバルなxの値(またはその欠如)は影響を受けません。ハードスコープのルールは、グローバルにxという名前が存在するかどうかを気にしません:ハードスコープ内でのxへの代入はローカルです(xがグローバルとして宣言されていない限り)。

次に考慮する明確な状況は、すでに x というローカル変数が存在する場合です。この場合、x = <value> は常にこの既存のローカル x に代入されます。これは、代入が同じローカルスコープ内で行われる場合、同じ関数本体内の内部ローカルスコープで行われる場合、または別の関数内にネストされた関数の本体内で行われる場合に当てはまります。これは、closure としても知られています。

sum_to 関数を使用します。この関数は、1 から n までの整数の合計を計算します。

function sum_to(n)
    s = 0 # new local
    for i = 1:n
        s = s + i # assign existing local
    end
    return s # same local
end

前の例と同様に、sum_toの最初のsへの代入は、関数の本体内で新しいローカル変数sを作成します。forループは関数スコープ内に独自の内部ローカルスコープを持っています。s = s + iが発生する時点で、sはすでにローカル変数であるため、代入は新しいローカルを作成するのではなく、既存のsを更新します。これをREPLでsum_toを呼び出すことでテストできます:

julia> function sum_to(n)
           s = 0 # new local
           for i = 1:n
               s = s + i # assign existing local
           end
           return s # same local
       end
sum_to (generic function with 1 method)

julia> sum_to(10)
55

julia> s # global
ERROR: UndefVarError: `s` not defined in `Main`

sは関数sum_toにローカルであるため、関数を呼び出してもグローバル変数sには影響を与えません。また、forループ内の更新s = s + iは、初期化s = 0によって作成された同じsを更新したに違いないことがわかります。なぜなら、1から10までの整数の合計55が正しく得られるからです。

少し冗長なバリエーションを作成し、sum_to_defと呼ぶことで、forループの本体が独自のスコープを持つという事実を掘り下げてみましょう。このバリエーションでは、sを更新する前に、s + iの合計を変数tに保存します。

julia> function sum_to_def(n)
           s = 0 # new local
           for i = 1:n
               t = s + i # new local `t`
               s = t # assign existing local `s`
           end
           return s, @isdefined(t)
       end
sum_to_def (generic function with 1 method)

julia> sum_to_def(10)
(55, false)

このバージョンは以前と同様に s を返しますが、@isdefined マクロを使用して、関数の最も外側のローカルスコープに t という名前のローカル変数が定義されているかどうかを示すブール値も返します。ご覧の通り、for ループの本体の外には t は定義されていません。これは再びハードスコープルールによるものです。t への代入が関数内で行われるため、ハードスコープが導入され、代入によって t はその出現するローカルスコープ、つまりループ本体内の新しいローカル変数になります。たとえグローバルに t という名前の変数があったとしても、何の違いもありません—ハードスコープルールはグローバルスコープの何によっても影響を受けません。

ループ本体のローカルスコープは、内部関数のローカルスコープと何ら変わりありません。これは、ループ本体を内部ヘルパー関数への呼び出しとして実装するようにこの例を書き換えることができ、同じように動作することを意味します。

julia> function sum_to_def_closure(n)
           function loop_body(i)
               t = s + i # new local `t`
               s = t # assign same local `s` as below
           end
           s = 0 # new local
           for i = 1:n
               loop_body(i)
           end
           return s, @isdefined(t)
       end
sum_to_def_closure (generic function with 1 method)

julia> sum_to_def_closure(10)
(55, false)

この例は、いくつかの重要なポイントを示しています:

  1. 内側の関数スコープは、他のネストされたローカルスコープと同じです。特に、変数が内側の関数の外で既にローカルであり、内側の関数内でそれに代入すると、外側のローカル変数が更新されます。
  2. 外部ローカルの定義が更新される場所の下にあっても、それは重要ではありません。ルールは同じままです。全体の外側のローカルスコープが解析され、そのローカルが決定されるのは、内部ローカルの意味が解決される前です。

このデザインは、一般的に内部関数にコードを移動させたり、内部関数からコードを取り出したりしても、その意味が変わらないことを意味します。これにより、クロージャを使用した言語の一般的なイディオムのいくつかが容易になります(参照:do blocks)。

より曖昧なケースに移りましょう。これはソフトスコープルールによってカバーされています。まず、greetとsum_to_def関数の本体をソフトスコープコンテキストに抽出することで探求します。最初に、greetの本体をハードではなくソフトなforループに入れて、REPLで評価してみましょう:

julia> for i = 1:3
           x = "hello" # new local
           println(x)
       end
hello
hello
hello

julia> x
ERROR: UndefVarError: `x` not defined in `Main`

グローバルな x が for ループが評価されるときに定義されていないため、ソフトスコープルールの最初の条項が適用され、x は for ループにローカルとして作成されます。したがって、ループが実行された後もグローバルな x は未定義のままです。次に、引数を n = 10 に固定して、sum_to_def の本体をグローバルスコープに抽出した場合を考えましょう。

s = 0
for i = 1:10
    t = s + i
    s = t
end
s
@isdefined(t)

このコードは何をしますか?ヒント:これはトリック質問です。答えは「それは依存します。」です。このコードがインタラクティブに入力されると、関数本体での動作と同じように振る舞います。しかし、コードがファイルに現れると、あいまいさの警告を表示し、未定義の変数エラーをスローします。まずはREPLで動作を見てみましょう:

julia> s = 0 # global
0

julia> for i = 1:10
           t = s + i # new local `t`
           s = t # assign global `s`
       end

julia> s # global
55

julia> @isdefined(t) # global
false

REPLは、ループ内の代入がグローバルに割り当てられるのか、新しいローカルが作成されるのかを、指定された名前のグローバル変数が定義されているかどうかに基づいて決定することによって、関数の本体にいることを近似します。指定された名前のグローバルが存在する場合、代入はそれを更新します。グローバルが存在しない場合、代入は新しいローカル変数を作成します。この例では、両方のケースが実際に見られます:

  • グローバルな t は存在しないため、t = s + i は for ループ内でローカルな新しい t を作成します;
  • グローバル変数 s があるので、s = t はそれに代入します。

2つ目の事実は、ループの実行がグローバルなsの値を変更する理由であり、1つ目の事実は、ループが実行された後にtがまだ未定義である理由です。さて、この同じコードをファイル内にあるかのように評価してみましょう:

julia> code = """
       s = 0 # global
       for i = 1:10
           t = s + i # new local `t`
           s = t # new local `s` with warning
       end
       s, # global
       @isdefined(t) # global
       """;

julia> include_string(Main, code)
┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable.
└ @ string:4
ERROR: LoadError: UndefVarError: `s` not defined in local scope

ここでは、include_stringを使用して、codeをファイルの内容であるかのように評価します。また、codeをファイルに保存し、そのファイルに対してincludeを呼び出すこともできます—結果は同じになります。ご覧のとおり、これはREPLで同じコードを評価するのとはかなり異なる動作をします。ここで何が起こっているのかを分解してみましょう:

  • グローバル s はループが評価される前に値 0 で定義されています。
  • 代入 s = t はソフトスコープ内で発生します。これは、関数本体や他のハードスコープ構造の外にある for ループです。
  • したがって、ソフトスコープルールの第二条が適用され、代入が曖昧であるため、警告が発生します。
  • 実行は続き、sをforループ本体にローカルにします。
  • sはforループにローカルであるため、t = s + iが評価されるときに未定義となり、エラーが発生します。
  • 評価はそこで停止しますが、もし s と @isdefined(t) に到達した場合、0 と false を返します。

これはスコープのいくつかの重要な側面を示しています:スコープ内では、各変数は一つの意味しか持つことができず、その意味は式の順序に関係なく決定されます。ループ内の式 s = t の存在は、s がループ内でローカルであることを意味し、これは t = s + i の右辺に出現する際にもローカルであることを意味します。たとえその式が最初に出現し、最初に評価されるとしてもです。ループの最初の行の s がグローバルで、2行目の s がローカルであると想像するかもしれませんが、それは不可能です。なぜなら、2行は同じスコープブロック内にあり、各変数は特定のスコープ内で一つの意味しか持てないからです。

On Soft Scope

ローカルスコープのルールについてはすべてカバーしましたが、このセクションを締めくくる前に、インタラクティブなコンテキストと非インタラクティブなコンテキストであいまいなソフトスコープのケースが異なる扱いを受ける理由について、いくつかの言葉を述べるべきかもしれません。考えられる明らかな質問が2つあります:

  1. なぜそれはどこでもREPLのように動作しないのですか?
  2. なぜファイルのどこでもそうやって動作しないのか?そして、警告をスキップしてもいいのでは?

Julia ≤ 0.6では、すべてのグローバルスコープは現在のREPLのように機能しました。ループ(またはtry/catch、またはstruct本体)内でx = <value>が発生した場合、関数本体(またはletブロックや内包表記)の外にあるとき、グローバルに名前付けされたxが定義されているかどうかに基づいて、xがループ内でローカルであるべきかどうかが決定されました。この動作には、関数本体内の動作にできるだけ近づけるという直感的で便利な利点があります。特に、関数の動作をデバッグしようとする際に、コードを関数本体とREPLの間で簡単に移動できるようにします。しかし、いくつかの欠点もあります。第一に、これは非常に複雑な動作です。多くの人々がこの動作について混乱し、説明するのが難しく理解するのが難しいと不満を持っていました。これは正当な指摘です。第二に、そしておそらくそれ以上に悪いのは、「スケールでのプログラミング」にとって悪いということです。このように一箇所に小さなコードの断片を見ると、何が起こっているのかは非常に明確です。

s = 0
for i = 1:10
    s += i
end

明らかに意図は既存のグローバル変数 s を変更することです。他に何を意味する可能性があるでしょうか?しかし、すべての実際のコードがこれほど短く明確であるわけではありません。以下のようなコードが実際にしばしば見られることがわかりました:

x = 123

# much later
# maybe in a different file

for i = 1:10
    x = "hello"
    println(x)
end

# much later
# maybe in yet another file
# or maybe back in the first one where `x = 123`

y = x + 234

ここで何が起こるべきかははっきりしません。x + "hello"がメソッドエラーであるため、xがforループ内でローカルであることが意図されている可能性が高いです。しかし、実行時の値や存在するメソッドによって変数のスコープを決定することはできません。Julia ≤ 0.6の動作では、特に懸念されるのは、誰かが最初にforループを書き、それがうまく動作していたが、後に別の誰かが遠くの新しいグローバルを追加した場合—おそらく別のファイル内で—コードの意味が突然変わり、騒がしく壊れるか、さらに悪いことに、静かに間違ったことをすることです。この種の"spooky action at a distance"は、良いプログラミング言語の設計が防ぐべきものです。

だから、Julia 1.0では、スコープのルールを簡素化しました:任意のローカルスコープ内で、すでにローカル変数でない名前に代入すると、新しいローカル変数が作成されました。これにより、ソフトスコープの概念が完全に排除され、スプーキーアクションの可能性も取り除かれました。ソフトスコープの削除により、多くのバグを発見し修正しました。この選択が正しかったことが証明されました。そして、多くの喜びがありました! まあ、実際にはそうではありません。なぜなら、ある人々は、今では次のように書かなければならないことに怒っていたからです:

s = 0
for i = 1:10
    global s += i
end

その中にある global アノテーションが見えますか?醜いですね。この状況は明らかに容認できません。しかし、実際には、この種のトップレベルコードに global を要求することには2つの主な問題があります:

  1. 関数の本体からコードをコピーしてREPLに貼り付けてデバッグするのはもはや便利ではありません。global アノテーションを追加し、その後元に戻すために再度削除する必要があります。
  2. 初心者はこのようなコードを書き、globalを使わずに、なぜ自分のコードが動作しないのか全く理解できません。彼らが受け取るエラーはsが未定義であるというもので、これはこの間違いを犯した人には何の助けにもなりません。

Julia 1.5以降、このコードはREPLやJupyterノートブックのようなインタラクティブなコンテキストではglobalアノテーションなしで動作します(Julia 0.6と同様に)。ファイルやその他の非インタラクティブなコンテキストでは、非常に直接的な警告が表示されます:

sへの割り当ては、同名のグローバル変数が存在するため、ソフトスコープ内であいまいです:sは新しいローカルとして扱われます。この警告を抑制するにはlocal sを使用するか、既存のグローバル変数に割り当てるにはglobal sを使用してください。

これにより、1.0の動作の「スケールでのプログラミング」の利点を保持しつつ、両方の問題に対処しています:グローバル変数は、遠くにあるコードの意味に不気味な影響を与えません;REPLではコピー&ペーストデバッグが機能し、初心者は問題を抱えません;誰かがglobalアノテーションを忘れたり、ソフトスコープ内でローカルによって既存のグローバルを偶然にシャドウイングした場合(それは混乱を招くでしょうが)、明確な警告が表示されます。

このデザインの重要な特性は、警告なしにファイル内で実行されるコードは、新しいREPLでも同じように動作するということです。そして逆に、REPLセッションをファイルに保存し、それがREPLでの動作と異なる場合、警告が表示されます。

Let Blocks

let 文は新しい ハードスコープ ブロックを作成し(上記を参照)、実行されるたびに新しい変数バインディングを導入します。変数は即座に割り当てられる必要はありません:

julia> var1 = let x
           for i in 1:5
               (i == 4) && (x = i; break)
           end
           x
       end
4

既存の値の場所に新しい値を再割り当てすることができるのに対し、let は常に新しい場所を作成します。この違いは通常重要ではなく、クロージャを介してスコープを超えて生き残る変数の場合にのみ検出可能です。let 構文は、カンマ区切りの一連の代入と変数名を受け入れます:

julia> x, y, z = -1, -1, -1;

julia> let x = 1, z
           println("x: $x, y: $y") # x is local variable, y the global
           println("z: $z") # errors as z has not been assigned yet but is local
       end
x: 1, y: -1
ERROR: UndefVarError: `z` not defined in local scope

代入は順番に評価され、右辺の各式は新しい左辺の変数が導入される前のスコープで評価されます。したがって、let x = xのように書くことは理にかなっています。なぜなら、2つのx変数は異なり、別々のストレージを持っているからです。ここにletの動作が必要な例があります:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           Fs[i] = ()->i
           global i += 1
       end

julia> Fs[1]()
3

julia> Fs[2]()
3

ここでは、変数 i を返す2つのクロージャを作成して保存します。しかし、常に同じ変数 i であるため、2つのクロージャは同じように動作します。let を使用して i の新しいバインディングを作成することができます:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           let i = i
               Fs[i] = ()->i
           end
           global 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とは異なる変数です。この特定の例は次のように等価です:

julia> let x = 1
           let x = 2
           end
           x
       end
1

Loops and Comprehensions

In loops and comprehensions, new variables introduced in their body scopes are freshly allocated for each loop iteration, as if the loop body were surrounded by a let block, as demonstrated by this example:

julia> Fs = Vector{Any}(undef, 2);

julia> for j = 1:2
           Fs[j] = ()->j
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

for ループまたは内包表記の反復変数は常に新しい変数です:

julia> function f()
           i = 0
           for i = 1:3
               # empty
           end
           return i
       end;

julia> f()
0

ただし、既存のローカル変数を反復変数として再利用することが時折便利です。これは、キーワード outer を追加することで便利に行うことができます:

julia> function f()
           i = 0
           for outer i = 1:3
               # empty
           end
           return i
       end;

julia> f()
3

Constants

変数の一般的な使用法は、特定の不変の値に名前を付けることです。このような変数は一度だけ割り当てられます。この意図は、const キーワードを使用してコンパイラに伝えることができます:

julia> const e  = 2.71828182845904523536;

julia> const pi = 3.14159265358979323846;

複数の変数を単一の const ステートメントで宣言できます:

julia> const a, b = 1, 2
(1, 2)

const 宣言は、グローバルスコープのグローバル変数にのみ使用するべきです。グローバル変数を含むコードの最適化は困難です。なぜなら、それらの値(または型)がほぼいつでも変わる可能性があるからです。グローバル変数が変更されない場合、const 宣言を追加することでこのパフォーマンスの問題を解決できます。

ローカル定数はかなり異なります。コンパイラはローカル変数が定数であるかどうかを自動的に判断できるため、ローカル定数の宣言は必要なく、実際には現在サポートされていません。

特別なトップレベルの割り当て、例えば function や struct キーワードによって行われるものは、デフォルトで定数です。

constは変数のバインディングにのみ影響を与えることに注意してください。変数はミュータブルなオブジェクト(配列など)にバインドされる可能性があり、そのオブジェクトは依然として変更される可能性があります。さらに、定数として宣言された変数に値を割り当てようとすると、次のシナリオが考えられます。

  • 定数を const キーワードなしで置き換えようとすることは許可されていません:
julia> const x = 1.0
1.0

julia> x = 1
ERROR: invalid assignment to constant x. This redefinition may be permitted using the `const` keyword.
  • すべての他の定数の定義は許可されていますが、重大な再コンパイルを引き起こす可能性があります:
julia> const y = 1.0
1.0

julia> const y = 2.0
2.0
Julia 1.12

julia 1.12以前は、定数の再定義は十分にサポートされていませんでした。同じ型の定数の再定義に制限されており、観察可能な誤った動作やクラッシュを引き起こす可能性がありました。julia 1.12以前のバージョンでは、定数の再定義は強く推奨されていません。詳細については、以前のjuliaバージョンのマニュアルを参照してください。

Typed Globals

Julia 1.8

Julia 1.8で型付きグローバルのサポートが追加されました。

定数として宣言されるのと同様に、グローバルバインディングも常に定数型であると宣言することができます。これは、実際の値を割り当てずに global x::T の構文を使用して行うことも、x::T = 123 のように割り当てる際に行うこともできます。

julia> x::Float64 = 2.718
2.718

julia> f() = x
f (generic function with 1 method)

julia> Base.return_types(f)
1-element Vector{Any}:
 Float64

グローバルへの任意の割り当てに対して、Juliaはまずconvertを使用して適切な型に変換しようとします:

julia> global y::Int

julia> y = 1.0
1.0

julia> y
1

julia> y = 3.14
ERROR: InexactError: Int64(3.14)
Stacktrace:
[...]

型は具体的である必要はありませんが、抽象型を使用した注釈は通常、パフォーマンス上の利点がほとんどありません。

一度グローバルが割り当てられるか、その型が設定されると、バインディングの型は変更できません:

julia> x = 1
1

julia> global x::Int
ERROR: cannot set type for global x. It already has a value or is already set to a different type.
Stacktrace:
[...]