変数のスコープ
ある変数のスコープ (scope) とは、その変数を参照できるコード領域のことです。変数のスコープがあると変数名の衝突を防ぐことができます。スコープの概念に難しいところはありません: 二つの関数が x
という引数を持っていたとしても、その二つの x
が同じものを指すことがないのはスコープのおかげです。同様に、異なるコードブロックに含まれる同名の変数が異なるものを指す場面は多く存在します。同名の変数が同じものを指す (あるいは指さない) のはいつかを決めるのがスコープ規則であり、この章ではスコープ規則を詳しく見ていきます。
特定の Julia の構文はスコープブロック (scope block) を作ります。スコープブロックは何らかの変数の集合に対するスコープとして使われるコード領域のことです。変数のスコープはソースコードの行番号で表されるのではなく、このスコープブロックで表されます。Julia は二種類のスコープブロックを持ちます: グローバルスコープ (global scope) とローカルスコープ (local scope) です。ローカルスコープはネストできます。さらに Julia のローカルスコープにはハードスコープ (hard scope) とソフトスコープ (soft scope) の区別があります。この区別は同じ名前のグローバル変数の隠蔽が許されるかどうかに影響します。
スコープ構文
スコープブロックを作成する構文は次の通りです:
構文 | この構文が作成するスコープ | この構文を使えるスコープ |
---|---|---|
module , baremodule
|
グローバル | グローバル |
struct
|
ローカル (ソフト) | グローバル |
for , while , try
|
ローカル (ソフト) | グローバルまたはローカル |
macro
|
ローカル (ハード) | グローバル |
let , 関数, 内包表記, ジェネレータ
|
ローカル (ハード) | グローバルまたはローカル |
begin
と if
がこの表に含まれていませんが、それはこれらの構文が新しいスコープを作成しないためです。三種類のスコープは微妙に異なる規則に従うので、以下ではその規則を説明します。
Julia は字句的スコープを使う言語です。これは関数のスコープが呼び出し側のスコープを受け継がず、関数が定義されたスコープを受け継ぐことを意味します。例えば次のコードにおける foo
の定義で使われる x
は、モジュール Bar
が作るグローバルスコープにおける x
を指します:
julia> module Bar
x = 1
foo() = x
end;
foo
が使われる場所のスコープにおける x
ではありません:
julia> import .Bar
julia> x = -1;
julia> Bar.foo()
1
つまり字句的スコープの言語では、特定のコード片に含まれる変数が指す値はプログラムの実行に依存せず、変数の定義だけから決定します。
他のスコープの内部にあるスコープからは、外側のスコープを全て "見る" ことができます。一方、外側のスコープから内側のスコープが持つ変数を見ることはできません。
グローバルスコープ
モジュールは新しいグローバルスコープを作成するので、他のモジュールが持つグローバルスコープと分離されます。モジュールから他のモジュールの変数を参照するには using
文または import
文を使うか、ドット構文による限定アクセス (qualified access) を使います。つまりモジュールは名前と値を関連付けるファーストクラスのデータ構造であるだけではなく、いわゆる名前空間 (namespace) でもあります。
モジュールが持つ変数束縛は外部から参照できますが、値の改変は変数を持つモジュールからのみ行えます。ただし非常用のハッチとして、eval
を使ってモジュール内部で任意のコードを評価して変数を変更することは常に可能です。ここから eval
を呼ばないコードは他のモジュールの束縛を改変しないことが保証されます。
モジュールの使用例を示します:
julia> module A
a = 1 # A のスコープにおけるグローバル変数
end;
julia> module B
module C
c = 2
end
b = C.c # 限定アクセスを使えば
# ネストされたグローバルスコープにアクセスできる
import ..A # モジュール A を利用可能にする
d = A.a
end;
julia> module D
b = a # D のグローバルスコープは A のものと異なるのでエラー
end;
ERROR: UndefVarError: a not defined
julia> module E
import ..A # モジュール A を利用可能にする
A.a = 2 # 下記のエラーが発生する
end;
ERROR: cannot assign variables in other modules
対話プロンプト (REPL) はモジュール Main
のグローバルスコープで動作します。
ローカルスコープ
大部分のコードブロックは新しくローカルスコープを作成します (詳しくは上の表を見てください)。一部の言語では新しい変数を使う前に明示的に宣言が必要ですが、明示的な宣言は Julia でも可能です: 任意のローカルスコープで local x
とすれば、x
という名前の変数が外のスコープにあるかどうかに関わらず、そのスコープにローカル変数 x
が宣言されます。全てのローカル変数をこのように宣言するのは長くて面倒なので、Julia は他の多くの言語と同様、ローカルスコープで起こる新しい変数への代入を新しいローカル変数の暗黙な宣言とみなします。多くの場合でこれは非常に直感的な動作をしますが、"直感的な" 動作によくあるように、詳細は素朴な想像と微妙に異なります。
ローカルスコープに x = <value>
という代入式が含まれると、Julia は代入式のある場所と x
という変数が既に存在するかどうかに応じて、次の規則で式の意味を決定します:
- 既存のローカル変数:
x
が既にローカル変数として存在するなら、そのローカル変数x
に値が代入されます。 - ハードスコープ:
x
がローカル変数として存在せず、かつ代入がハードスコープを作る構文 (let
ブロック・関数またはマクロの本体・内包表記・ジェネレータ) の内部で起きているなら、x
という名前の新しいローカル変数が代入式と同じスコープに作成されます。 -
ソフトスコープ:
x
がローカル変数として存在せず、かつ代入式を包む構文が作るローカルスコープが全てソフト (ループ・try/catch
ブロック・struct
ブロック) なら、動作はグローバル変数x
が定義されているかによって決まります:- もしグローバル変数
x
が定義されていないなら、新しいローカル変数x
が代入式と同じスコープで定義されます。 - もしグローバル変数
x
が定義されているなら、代入式は曖昧とみなされ、さらに場合分けが起きます:- 対話的でない状況 (ファイルや
eval
の実行) では、曖昧性の警告と共にローカル変数が作成されます。 - 対話的な状況 (REPL や Jupyter Notebook) では、グローバル変数
x
への代入が起こります。
- 対話的でない状況 (ファイルや
- もしグローバル変数
ここから対話的でない状況ではハードスコープとソフトスコープが等価な振る舞いをすることが分かります。ただしソフトスコープで暗黙の内に定義される (local x
として宣言されていない) ローカル変数がグローバル変数を隠す場合には警告が出力されます。対話的な状況では、利便性のため複雑なヒューリスティックが使われます。以降の例で詳しく触れます。
規則が分かったので、いくつか例を見ていきます。これから示す例は全て新しい REPL セッションで実行されることを想定しています。つまり各スニペットが持つグローバル変数はそこで定義されたものだけです。
まずは一番簡単な、ハードスコープ内部における代入から始めましょう。次の例は関数の本体において、存在しない変数に対して代入を行っています:
julia> function greet()
x = "hello" # 新しいローカル変数
println(x)
end
greet (generic function with 1 method)
julia> greet()
hello
julia> x # グローバル変数
ERROR: UndefVarError: x not defined
greet
関数内部の代入 x = "hello"
は、関数をスコープとする新しいローカル変数 x
を作成します。この振る舞いには次の二つの事実が関係します: この代入が (関数定義によって作成される) ローカルスコープで起こっていることと、代入の前に x
という名前のローカル変数が存在しないことです。
greet
関数で定義される x
はローカル変数なので、グローバル変数 x
の定義の有無には影響を受けません。greet
を定義して呼び出す前に x = 123
を定義する例を次に示します:
julia> x = 123 # グローバル変数
123
julia> function greet()
x = "hello" # 新しいローカル変数
println(x)
end
greet (generic function with 1 method)
julia> greet()
hello
julia> x # グローバル変数
123
greet
関数内部の x
はローカル変数なので、その値はグローバル変数 x
の値 (および x
が存在するかどうか) に影響を受けません。関数定義が作成するハードスコープの規則は x
という名前のグローバル変数が存在するかどうかを気にしないので、x
に対する代入はローカルとなります (ただし x
がグローバル変数と宣言された場合には別です)。
次に考えるのは、x
というローカル変数が既に存在している状況です。この場合 x = <vlaue>
は必ず既存のローカル変数への代入となります。次の sum_to
関数は 1
から n
までの自然数の和を計算します:
function sum_to(n)
s = 0 # 新しいローカル変数
for i = 1:n
s = s + i # 既存のローカル変数に対する代入
end
return s # 同じローカル変数
end
一つ前の例と同じように、sum_to
の最初の行にある代入式は新しいローカル変数 s
を作成します。for
ループは新しくローカルスコープを作成しますが、これは関数のスコープに含まれます。そのため s = s + i
が評価されるとき s
はローカル変数として存在し、この代入式は新しく変数を作らずに既存の s
を更新します。REPL で sum_to
を呼び出せばこのことを確認できます:
julia> function sum_to(n)
s = 0 # 新しいローカル変数
for i = 1:n
s = s + i # 既存のローカル変数に対する代入
end
return s # 同じローカル変数
end
sum_to (generic function with 1 method)
julia> sum_to(10)
55
julia> s # グローバル変数
ERROR: UndefVarError: s not defined
s
は sum_to
におけるローカル変数なので、この関数を呼び出してもグローバル変数 s
は影響を受けません。また for
ループ内の s = s + i
が s = 0
の s
を更新していることは、sum_to(10)
が 1 から 10 までの自然数の和 55 を計算していることからも分かります。
for
ループの本体が新しくスコープを作っていることを確認するために、上のコードを少し冗長にしてみましょう。次の関数 sum_to2
は、s
を和 s + i
で更新する前に変数 t
へ一度保存します:
julia> function sum_to2(n)
s = 0 # new local
for i = 1:n
t = s + i # 新しいローカル変数 t
s = t # 既存のローカル変数 s に対する代入
end
return s, @isdefined(t)
end
sum_to2 (generic function with 1 method)
julia> sum_to2(10)
(55, false)
このバージョンは和 s
に加えて t
という名前のローカル変数が関数の一番外側のローカルスコープで定義されているかを表す真偽値を返します。実行例から分かるように、t
は for
ループの本体より外側では定義されていません。ここでも理由はハードスコープの規則です: t
に対する代入は関数、つまりハードスコープの内側で起こっているので、この代入は新しいローカル変数 t
を現在のスコープ、つまりループ本体のスコープに作成します。t
という名前のグローバル変数があったとしても、この関数の動作は変わりません ──ハードスコープの規則はグローバルスコープに一切影響を受けないからです。
次はソフトスコープの規則が絡むさらに微妙なケースを見ましょう。greet
関数と sum_to2
関数の本体をソフトスコープのコンテキストに移動させれば、ソフトスコープの挙動を確認できます。まず greet
関数の本体を for
ループ ──ソフトスコープを作る構文── の中に移動させ、それを REPL で評価します:
julia> for i = 1:3
x = "hello" # 新しいローカル変数
println(x)
end
hello
hello
hello
julia> x
ERROR: UndefVarError: x not defined
for
ループが評価されるときグローバル変数 x
は定義されていないので、ソフトスコープの最初の規則が適用され、x
がローカル変数として定義されます。そのためループの実行が終われば x
は未定義となります。
次は sum_to2
の本体をグローバルスコープに取り出して実行してみましょう。n
は 10
に固定します:
s = 0
for i = 1:10
t = s + i
s = t
end
s
@isdefined(t)
このコードは何をするでしょうか? ヒント: ひっかけ問題です。
答えは「場合による」です。このコードを対話的に入力すれば、関数のときと同じ振る舞いとなります。しかしこのコードをファイルから読み込むと、曖昧であるという警告と未定義変数のエラーが発生します。まず REPL で何が起こるかを見ます:
julia> s = 0 # グローバル変数
0
julia> for i = 1:10
t = s + i # 新しいローカル変数 t
s = t # グローバル変数 s への代入
end
julia> s # グローバル変数
55
julia> @isdefined(t) # グローバル変数
false
代入式がグローバル変数への代入なのか新しいローカル変数の作成なのか判断するとき、REPL は左辺の名前のグローバル変数が定義されているかどうかを確認します。その名前のグローバル変数が存在すれば代入式でそのグローバル変数が更新され、存在しなければ代入式で新しいローカル変数が作成されます。このコードでは二つのケースが両方表れています:
t
という名前のグローバル変数は存在しないので、t = s + i
はfor
ループの内部で使えるローカル変数t
を新しく作成します。s
という名前のグローバル変数は存在するので、s = t
はグローバル変数s
への代入となります。
二つ目の事実が s
が更新される理由であり、一つ目の事実が t
がループの後で未定義となる理由です。
次はこのコードがファイルに書かれているとして評価してみます:
julia> code = """
s = 0 # グローバル変数
for i = 1:10
t = s + i # 新しいローカル変数 t
s = t # 新しいローカル変数 s (警告が出る)
end
s, # グローバル変数
@isdefined(t) # グローバル変数
""";
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
ここで使っている include_string
は文字列をファイルの内容であるかのように実行する関数です。code
をファイルに保存してからそのファイルを include
しても同じ結果となります。実行例から分かるように、こうしたときの動作は REPL で同じコードを評価したときとは大きく異なります。何が起きているかを順に説明します:
- ループが評価される前にグローバル変数
s
が定義され、値0
で初期化されます。 - 代入
s = t
がソフトスコープで起こります ──このfor
ループはハードスコープを作る構文 (関数など) に含まれていないためです。 - このため代入
s = t
にはソフトスコープの二番目の規則が適用され、代入が曖昧であるという警告が発生します。 s
がfor
ループ本体におけるローカル変数となったまま実行が続きます。s
がfor
ループにおけるローカル変数なので、t = s + 1
が評価されるときs
は未定義であり、エラーが発生します。- 実行はここで止まりますが、もし
s
と@isdefined(t)
まで実行できたとすれば0
とfalse
が返ります。
この例にはスコープの重要な特徴が示されています: スコープにおいて一つの変数は一つの意味しか持つことができず、変数の意味は評価順序に関係なく決定されます。ループ中の s = t
という式が s
をループ中でだけ有効なローカル変数に定めるので、二回目の反復で t = s + i
の右辺に表れる s
は (ループの最初の行にあり最初に評価されるにもかかわらず) ローカルです。ループ一行目の s
をグローバルにして二行目の s
をローカルにすればよいと思うかもしれませんが、同じスコープに含まれる同じ変数は同じ意味を持たなければならないので、そうはできません。
ソフトスコープについて
これでローカルスコープの規則を全て説明できました。ただこの章を終える前に、曖昧なソフトスコープが対話的なときと対話的でないときで異なる振る舞いをする理由についてもう少し話しておくべきでしょう。明らかな疑問が二つあります:
- どんなときでも REPL のような動作をしてはなぜいけないのか?
- どんなときでもファイルのような動作をしてはなぜいけないのか? 警告は出さなくても構わないのでは?
バージョン 0.6 までの Julia では、全てのグローバルスコープが現在の REPL のように動作しました。x = <value>
がループ (または try/catch
や struct
の本体) にあり、かつ関数の本体 (または let
ブロックや内包表記) に含まれないなら、x
がループにローカルかどうかはグローバル変数 x
が定義されているかどうかで決まっていたということです。
この振る舞いは関数本体と似ているので、直感的で便利という利点があります。ただ欠点もあります。まず、この振る舞いはかなり複雑です: 説明を読んでもよく分からないという意見が長年にわたって多くの人から聞かれました。もっともな意見です。次に、間違いなくこれよりも悪い欠点として、「大規模な」プログラミングにおいてこの振る舞いは問題になります。次のようなコードが一つの場所に書かれていれば、何が起きているかは一目瞭然です:
s = 0
for i = 1:10
s += i
end
このコードが既存のグローバル変数 s
を更新しているのは明らかです。他に何を意味できるでしょうか? しかし、現実世界のコードはこれほど短くもなければ明快でもありません。私たちは次のようなコードに何度も遭遇しました:
x = 123
# ずっと後、あるいは別のファイル
for i = 1:10
x = "hello"
println(x)
end
# ずっと後、あるいはさらに別のファイル
# おそらく x = 123 のときに
y = x + 234
このコードの意図は明確ではありません。123 + "hello"
はメソッドエラーなので、どうやら for
ループ内の x
はローカル変数として書かれたようです。しかし実行時の値とそのときに利用可能なメソッドを使って変数のスコープを決めることはできません。バージョン 0.6 以前の Julia であり得たのが、for
ループを最初に書き、そこで目当てのコードを完成させ、それから遠く離れた別の場所 ──おそらくは別のファイル── をいじると最初のコードがエラーを出す、さらに悪いことにはエラーを出さずに振る舞いが変化するという現象です。この種の「不気味な遠隔作用」は優れたプログラミング言語が設計によって防ぐべきバグです。
これを受けて Julia 1.0 ではスコープの規則が単純化されました: 任意のローカルスコープにおいて、変数に対する代入は新しいローカル変数を作成するようになったのです。これによりソフトスコープという概念が完全に無くなり、不気味な遠隔作用も姿を消しました。ソフトスコープを削除したおかげで大量のバグが発見・修正され、この判断は正しかったのだと喜んだものです...
...しかし全員がそう思ったわけではなかったようです。一部の人は次のようなコードなど書きたくないと不満を口にしました:
s = 0
for i = 1:10
global s += i
end
global
という注釈に注目してください。あぁ恐ろしい、こんな見苦しいコードには耐えられない! ...ですが真面目に考えても、こういったトップレベルのコードに対して global
を必須とする設計には二つの大きな問題があります:
-
関数の本体に含まれるコードをデバッグのために REPL へコピペしても、
global
注釈を入れなければ正しく動きません。さらにデバッグした後はglobal
を取り除かなければなりません。 -
初心者は
global
を入れ忘れたコードを書きがちですが、そのコードが予想通りに動かない理由を全く理解できません。そういったコードに対して表示されるエラーは「s
が定義されていない」であり、これだけではglobal
を入れ忘れたことに気が付かないでしょう。
現在の Julia 1.5 では、このコードは REPL や Jupyter Notebook なら global
注釈無しに正しく動作します (つまり Julia 0.6 と同様です)。またファイルなどの非対話的な状況では、非常に直接的な警告が表示されます:
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 usinglocal s
to suppress this warning orglobal s
to assign to the existing global variable.(
s
という名前のグローバル変数が存在するので、このソフトスコープにおけるs
への代入は曖昧です。s
は新しいローカル変数として扱われます。local s
としてこの警告を消すか、global s
として既存のグローバル変数への代入に変更してください。)
こうすると 1.0 が持つ「大規模プログラミング」におけるメリットを保持しつつ二つの問題を解決できます: グローバル変数が遠くのコードの意味を変えることはなく、デバッグのために REPL に貼り付けられたコードはそのまま動きます。さらに初心者が躓くこともありません: global
注釈を忘れたり、既存のグローバル変数をソフトスコープのローカル変数で隠したりすると、以前のような何も情報が得られない警告ではなく、丁寧で明快な警告を受けるからです。
この設計の重要な特徴が、ファイルから実行したときに警告が出ない任意のコードは新しい REPL で実行しても同じように振る舞うという事実です。逆に REPL で書いたコードをファイルに保存して実行すると振る舞いが変わる可能性があり、変わる場合には警告が発生します。
let
ブロック
ローカル変数への代入とは異なり、let
文は実行されるたびに新しい変数束縛を作成します。代入は既に存在する名前の変数に対しては保存場所を使い回しますが、let
は既に存在する名前の変数に対しても新しい保存場所を作成するということです。通常この違いは重要ではなく、唯一違いが目に見えて分かるのはクロージャによって変数が普通よりも長く生存したときだけです。
let
の構文は変数の名前と代入の並びをコンマで分けて複数書くことを許しています:
julia> x, y, z = -1, -1, -1;
julia> let x = 1, z
println("x: $x, y: $y") # x はローカル変数, y はグローバル変数
println("z: $z") # z に値が代入されていないのでエラー (z はローカル変数)
end
x: 1, y: -1
ERROR: UndefVarError: z not defined
let
の最初に付く代入は順番に評価され、右辺の評価は左辺の変数がスコープに入る前に起こります。そのため let x = x
と書いてもエラーではありません: 二つの 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
を返す二つのクロージャを作成して保存しています。しかしクロージャに含まれる変数 i
は同じなので、二つのクロージャは同じ値を返します。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
は異なる変数です。
ループと内包表記
ループと内包表記 (comprehension) において、本体に含まれる新しい変数はループの反復のたびに新しく作成されます。これはループ本体が let
ブロックで囲まれているかのような振る舞いであり、次の例の通りです:
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
# 何もしない
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
定数
変数はよく特定の不変な値に名前を付けるのに使われ、そういった変数に対する代入は一度だけ行われます。キーワード 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 Array{Int64,1}:
1
julia> a = [1]
WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors.
1-element Array{Int64,1}:
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