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`

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

少し冗長なバリエーションを作成し、これを sum_to_def と呼びましょう。この中で、s + i の合計を s を更新する前に変数 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を参照)。

より曖昧なケースに移りましょう。これはソフトスコープルールによってカバーされています。まず、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 がグローバルで、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

It's far less clear what should happen here. Since x + "hello" is a method error, it seems probable that the intention is for x to be local to the for loop. But runtime values and what methods happen to exist cannot be used to determine the scopes of variables. With the Julia ≤ 0.6 behavior, it's especially concerning that someone might have written the for loop first, had it working just fine, but later when someone else adds a new global far away—possibly in a different file—the code suddenly changes meaning and either breaks noisily or, worse still, silently does the wrong thing. This kind of "spooky action at a distance" is something that good programming language designs should prevent.

だから、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:
[...]