Scope of Variables

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

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

Scope constructs

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

ConstructScope typeAllowed within
module, baremoduleglobalglobal
structlocal (soft)global
for, while, trylocal (soft)global, local
macrolocal (hard)global
functions, do blocks, let blocks, comprehensions, generatorslocal (hard)global, local

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

ジュリアは 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 ブロック、または struct ブロック)であるなら、動作はグローバル変数 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 + iの合計を変数tに保存してからsを更新します。

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を参照)。

より曖昧なケースに移りましょう。ソフトスコープルールによってカバーされるこれらのケースを探ります。まず、greetsum_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`

グローバルな xfor ループが評価されるときに定義されていないため、ソフトスコープルールの最初の条項が適用され、xfor ループにローカルとして作成されます。したがって、ループが実行された後もグローバルな 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 + ifor ループ内でローカルな新しい 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 ループというソフトスコープ内で発生します。
  • したがって、ソフトスコープルールの第二条項が適用され、代入が曖昧であるため、警告が発生します。
  • 実行は続行され、sforループ本体にローカルになります。
  • sforループにローカルであるため、t = s + iが評価されるときに未定義となり、エラーが発生します。
  • 評価はそこで停止しますが、もしsに到達し、@isdefined(t)が真であれば、0falseを返します。

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

On Soft Scope

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

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

Julia ≤ 0.6では、すべてのグローバルスコープは現在のREPLのように動作しました。ループ(またはtry/catch、またはstruct本体)内で、関数本体(またはletブロックや内包表記)の外でx = <value>が発生した場合、グローバルに名付けられた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"がメソッドエラーであるため、xforループ内でローカルであることが意図されている可能性が高いです。しかし、ランタイムの値や存在するメソッドによって変数のスコープを決定することはできません。Julia ≤ 0.6の動作では、特に懸念されるのは、誰かが最初にforループを書き、それがうまく動作していたが、後に別の誰かが遠くの新しいグローバルを追加した場合—おそらく別のファイルで—コードの意味が突然変わり、騒がしく壊れるか、さらに悪いことに、静かに間違ったことをする可能性があることです。この種の"spooky action at a distance"は、良いプログラミング言語の設計が防ぐべきものです。

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

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

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

  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 宣言を追加することでこのパフォーマンスの問題を解決できます。

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

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

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

  • 新しい値が定数の型と異なる型を持っている場合、エラーが発生します:
julia> const x = 1.0
1.0

julia> x = 1
ERROR: invalid redefinition of constant x
  • 新しい値が定数と同じ型である場合、警告が表示されます:
julia> const y = 1.0
1.0

julia> y = 2.0
WARNING: redefinition of constant y. This may fail, cause incorrect answers, or produce other errors.
2.0
  • 変数の値が変更されない場合、メッセージは表示されません。
julia> const z = 100
100

julia> z = 100
100

最後のルールは、変数のバインディングが変更される場合でも、不変オブジェクトに適用されます。例えば:

julia> const s1 = "1"
"1"

julia> s2 = "1"
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x00000000132c9638
 Ptr{UInt8} @0x0000000013dd3d18

julia> s1 = s2
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x0000000013dd3d18
 Ptr{UInt8} @0x0000000013dd3d18

しかし、可変オブジェクトの場合、警告は期待通りに印刷されます:

julia> const a = [1]
1-element Vector{Int64}:
 1

julia> a = [1]
WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors.
1-element Vector{Int64}:
 1

const 変数の値を変更することは時々可能ですが、強く推奨されておらず、インタラクティブな使用中の便宜のためだけに意図されています。定数を変更すると、さまざまな問題や予期しない動作を引き起こす可能性があります。たとえば、メソッドが定数を参照していて、その定数が変更される前にすでにコンパイルされている場合、古い値を使い続ける可能性があります。

julia> const x = 1
1

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

julia> f()
1

julia> x = 2
WARNING: redefinition of constant x. This may fail, cause incorrect answers, or produce other errors.
2

julia> f()
1

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:
[...]