UWSCではclass/module構文でモジュールを定義することができました。class構文もmodule構文も機能としては同一で、機能的にはモジュールなのだからじゃあclassってなんだったの?という感じですがこれは歴史的経緯でこうなっています。
UWSCRではどうせならclassをもう少しそれっぽくしてみたいということで、仕様を大幅に変更しました。moduleはほぼそのままです。
moduleの変更点
module構文はUWSCのそれとほぼ同等ですが、以下の変更点があります。
どこにでも書ける
ユーザー定義関数と同様に、スクリプト下部に書かなければいけないという制限は撤廃されています。
globalの拡張
特殊変数global
が拡張されています。UWSCではglobal.関数名()
とすることでグローバル関数を明示的に呼び出すことができましたが、UWSCRでは変数や定数も呼び出せるようになっています。
const Hoge = "global"
module MyModule
const Hoge = "module"
function module_hoge()
result = Hoge
fend
function global_hoge()
result = global.Hoge
fend
endmodule
print MyModule.module_hoge() // module
print MyModule.global_hoge() // global
プライベート関数の定義
UWSCではプライベート関数を定義することができませんでしたが、UWSCRでは無名関数を使うことでプライベート関数を実現できます。
module MyModule
dim private = function()
result = "private"
fend
function call_private()
result = private()
fend
endmodule
print MyModule.call_private() // private
print MyModule.private() // エラーになる(※)
なんと執筆時点のバージョン0.14.0
だとプライベート関数がエラーにならないというバグがありました!上記コードの※部分はprivateと出力されてしまいます。なんてこった…。
0.15.0
までには修正します…!
classの変更点
class構文の記述方法には特に変更はありませんが、機能的には一切互換性がありません。moduleのような呼び出しが一切できなくなっていますが、代わりにクラス名()
とすることでクラスのインスタンスを得られるようになっています。
class定義
classを定義する場合はコンストラクタ(クラス名と同名の関数)が必須です。
class MyClass
dim name
// コンストラクタ (必須)
procedure MyClass(name: string)
this.name = name
fend
endclass
moduleのコンストラクタはスクリプト開始時 (厳密にはmodule評価時) に実行されていましたが、classのコンストラクタはインスタンス作成時に実行されます。
インスタンスの作成
クラス名()
というようにクラスを関数のように呼び出すとクラスインスタンスが返ります。このときコンストラクタが呼び出されます。
class MyClass
dim name
// コンストラクタ
// 文字列を受けて自身のnameに代入する
procedure MyClass(name: string)
this.name = name
fend
function name()
result = this.name
fend
endclass
// コンストラクタに"stuncloud"を渡す
// 戻り値がクラスインスタンスになる
me = MyClass("stuncloud")
// メソッドを呼ぶ
print me.name() // stuncloud
デストラクタ
_class名_()
という名前の関数を定義するとデストラクタになります。デストラクタは引数を取りません。
デストラクタは原則インスタンスへの参照が全て失われた場合に実行されます。それ以外にも以下の状況で実行されます。
- インスタンス変数に
NOTHING
を代入した場合 (この場合インスタンスのコピーがあっても全て破棄される) -
with
式でコンストラクタを呼んだ場合でendwith
に到達したとき - スコープを抜ける際に、すべてのインスタンスがそのスコープのローカル変数だった場合
- スクリプト終了時
class MyClass
dim name
// コンストラクタ
procedure MyClass(name: string)
this.name = name
fend
// デストラクタ
procedure _MyClass_()
print "good bye, <#name>"
fend
function name()
result = this.name
fend
endclass
hoge = MyClass("hoge")
// インスタンス変数に別の値を代入
// すべての参照が失われるのでデストラクタが実行される
hoge = "" // good by, hoge
fuga = MyClass("fuga")
// インスタンスのコピーを作っておく
fuga2 = fuga
// NOTHINGを代入
// コピーも含めてインスタンスを破棄するためデストラクタが実行される
fuga = NOTHING // good by, fuga
print fuga2 // NOTHING
// with式がコンストラクタならendwithで破棄される
with MyClass("piyo")
print .name() // piyo
endwith // good bye, piyo
…と書いたもののなんと執筆時点のバージョン0.14.0
ではデストラクタが動作していませんでした!さらにNOTHING
を代入してしまうとデッドロックします!ヤバイ!
これも0.15.0
までに修正します。
0.14.0で発生している数々のバグについて
バージョン0.12.1
でmodule/classに関するバグを修正した際に、実はかなり大掛かりな変更が行われていました。そこで様々な不具合を含んでしまっていたのですが、その後の検証やテストが不十分であったためそれらが検知できていませんでした。このアドベントカレンダーの記事を書くに当たって記事内のコードは必ず動作確認をするのですが、そこで初めてこれらのバグが表面化しました。
これらは必ず次回リリース予定の0.15.0
で修正します。この記事を読んでくださっている方にはご迷惑をおかけしますが、0.14.0
でのサンプルコードの実行はお控えいただくようお願いいたします。