Scope of Variables
変数のスコープは、変数がアクセス可能なコードの領域です。変数のスコーピングは、変数名の衝突を避けるのに役立ちます。この概念は直感的です:2つの関数は、両方ともx
という引数を持つことができますが、2つのx
が同じものを指すわけではありません。同様に、異なるコードブロックが同じ名前を使用しても、同じものを指さない多くのケースがあります。同じ変数名が同じものを指すかどうかのルールはスコープルールと呼ばれ、このセクションではそれを詳細に説明します。
言語の特定の構文はスコープブロックを導入します。これは、特定の変数のセットがスコープとして適用されるコードの領域です。変数のスコープは任意のソース行のセットではなく、常にこれらのブロックのいずれかに一致します。Juliaには主に2種類のスコープがあります。グローバルスコープとローカルスコープです。後者はネスト可能です。また、Juliaには「ハードスコープ」を導入する構文と「ソフトスコープ」を導入する構文の区別があります。これは、同じ名前のグローバル変数shadowingが許可されるかどうかに影響します。
Scope constructs
スコープブロックを導入する構文は次のとおりです:
Construct | Scope type | Allowed within |
---|---|---|
module , baremodule | global | global |
struct | local (soft) | global |
for , while , try | local (soft) | global, local |
macro | local (hard) | global |
functions, do blocks, let blocks, comprehensions, generators | local (hard) | global, local |
この表に明らかに欠けているのは begin blocks と if 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
が既に指しているものに基づいて、式の意味を決定するために以下のルールを適用します:
既存のローカル:
x
が すでにローカル変数 である場合、既存のローカルx
に代入されます;ハードスコープ: もし
x
が すでにローカル変数でない 場合、ハードスコープ構造(すなわちlet
ブロック、関数またはマクロの本体、コンプリヘンション、またはジェネレーター)の内部で代入が行われると、代入のスコープ内に新しいローカル変数x
が作成される;ソフトスコープ: もし
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)
この例は、いくつかの重要なポイントを示しています:
- 内側の関数スコープは、他のネストされたローカルスコープと同じです。特に、変数が内側の関数の外で既にローカルであり、内側の関数内でそれに代入すると、外側のローカル変数が更新されます。
- 外部ローカルの定義が更新される場所の下にあっても、それは重要ではありません。ルールは同じままです。全体の囲むローカルスコープが解析され、そのローカルが決定されてから、内部ローカルの意味が解決されます。
このデザインは、一般的にコードを内部関数に移動したり、内部関数から移動させたりしてもその意味が変わらないことを意味しており、クロージャを使用した言語の一般的なイディオムのいくつかを容易にします(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つあります:
- なぜそれはどこでもREPLのように動作しないのですか?
- なぜファイルのどこでもそうやって動作しないのか?そして、警告をスキップしてもいいのでは?
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
を要求することには主に二つの問題があります:
- 関数の本体からコードをコピーしてREPLに貼り付けてデバッグするのはもはや便利ではありません。
global
アノテーションを追加し、その後元に戻す必要があります。 - 初心者はこの種のコードを書くときに
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
は変数のバインディングにのみ影響を与えることに注意してください。変数はミュータブルなオブジェクト(配列など)にバインドされる可能性があり、そのオブジェクトは依然として変更される可能性があります。さらに、定数として宣言された変数に値を割り当てようとすると、次のシナリオが考えられます。
- 新しい値が定数の型と異なる型を持っている場合、エラーが発生します:
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で型付きグローバルのサポートが追加されました。
定数として宣言されるのと同様に、グローバルバインディングも常に定数型であると宣言することができます。これは、実際の値を割り当てずに 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:
[...]