変数

badge

変数は代数の一種です。Ergにおける代数―紛れがなければ単に変数と呼ばれることもあります―とは、オブジェクトを名前付けしてコード中の別の場所から利用できるようにする機能を指します。

変数は以下のように定義します。 nの部分を変数名(または、識別子)、=を代入演算子、1の部分を代入値と呼びます。

n = 1

このようにして定義したnは、以降整数オブジェクトである1を示す変数として使用できます。このシステムを代入(または束縛)といいます。 いま1はオブジェクトであると述べました。オブジェクトが何であるかは後述しますが、今は代入できるもの、すなわち代入演算子(=など)の右側におけるものとしておきます。

変数の「型」を指定したい場合は以下のようにします。型とは、これも後述しますが、大まかにはオブジェクトの属する集合です。 ここでnは自然数(0以上の整数)に属する、すなわちNat型であると指定しています。

n: Nat = 1

他の言語とは違い、多重代入はできないので注意してください。

# NG
l1 = l2 = [1, 2, 3] # SyntaxError: multiple assignment not allowed

このようなことをしたい場合、.clone()を使います。これはオブジェクトのコピーを作成します。

# OK
l1 = [1, 2, 3]
l2 = l1.clone()

また、変数への再代入もできません。その代わりに使える機能、すなわち可変な状態を保持する機能については後述します。

i = 1
i = i + 1 # AssignError: cannot assign twice

内側のスコープで同じ名前の変数を定義できますが、上に被せているだけで、値を破壊的に書き換えているわけではありません。外側のスコープに戻れば値も戻ります。 これはPythonの「文」のスコープとは違う挙動なので注意してください。 このような機能は一般にシャドーイングと言います。ただし他言語のシャドーイングとは違い、同一スコープではシャドーイングできません。

簡単に言うと、Ergでは変数が勝手に書き換えられていないか心配する必要はないのです。

x = 0
# x = 1 # AssignError: cannot assign twice
if x.is_zero(), do:
    x = 1 # 外側のxとは同名の別物になる
    assert x == 1
assert x == 0

以下は一見すると可能なように思えますが、やはりできません。これは技術的な制約ではなく、設計判断です。

x = 0
if x.is_zero(), do:
    x = x + 1 # NameError: cannot define variables refer to variables with the same name
    assert x == 1
assert x == 0

定数

定数も代数の一種です。識別子を大文字で始めると定数として扱われます。一度定義したら変わらないので、定数と呼ばれます。 Nの部分を定数名(または、識別子)と呼びます。その他は変数と同じです。

N = 0
if True, do:
    N = 1 # AssignError: constants cannot be shadowed
    pass()

定数は定義されたスコープ以降では不変になります。シャドーイングもできません。この性質から、定数はパターンマッチで使用できます。 パターンマッチについては後に説明します。

定数は、数学的定数、外部リソースに関する情報など不変な値に対して使用すると良いでしょう。 以外のオブジェクトは、オールキャップス(全ての文字を大文字にするスタイル)にするのが一般的です。

PI = 3.141592653589793
URL = "https://example.com"
CHOICES = ["a", "b", "c"]
PI = 3.141592653589793
match! x:
    PI => print! "π"
    other => print! "other"

上のコードはx3.141592653589793のときπを出力します。xを他の数字に変えると、otherを出力します。

定数には代入できないものがあります。可変オブジェクトなどです。詳しくは後述しますが、可変オブジェクトは内容を変更することができるオブジェクトです。 これは定数には定数式のみを代入できるという規則があるためです。定数式についても後述することとします。

X = 1 # OK
X = !1 # TypeError: cannot define Int! object as a constant
X = input!() # SyntaxError: not a constant expression

代数の削除

Del関数を使うことで、代数を削除することが出来ます。その代数に依存している(その代数の値を直接参照している)他の代数もまとめて削除されます。

x = 1
y = 2
z = 3
f a = x + a

assert f(2) == 3
Del x # xを直接参照しているためfも削除される
Del y, z

f(2) # NameError: f is not defined (deleted in line 6)

ただし、Delによって削除できるのはモジュール内で定義された代数のみです。Trueなどの組み込み定数は削除できません。

Del True # TypeError: cannot delete built-in constants
Del print! # TypeError: cannot delete built-in variables

Delによって削除した名前は、その後に再利用することが出来ます。

付録: 代入と同値性

注意として、x = aであるとき、x == aとは限らない。例としてはFloat.NaNがある。これはIEEE 754により定められた正式な浮動小数点数の仕様である。

x = Float.NaN
assert x != Float.NaN
assert x != x

その他、そもそも同値関係が定義されていないオブジェクトも存在する。

f = x -> x**2 + 2x + 1
g = x -> (x + 1)**2
f == g # TypeError: cannot compare function objects

C = Class {i: Int}
D = Class {i: Int}
C == D # TypeError: cannot compare class objects

厳密に言うと=は右辺値をそのまま左辺の識別子に代入するわけではない。 関数オブジェクトやクラスオブジェクトの場合、オブジェクトに変数名の情報を与えるなどの「修飾」を行う。 ただし構造型の場合はその限りではない。

f x = x
print! f # <function f>
g x = x + 1
print! g # <function g>

C = Class {i: Int}
print! C # <class C>