moduleとclass

UWSCではclass/module構文でモジュールを定義することができました。class構文もmodule構文も機能としては同一で、機能的にはモジュールなのだからじゃあclassってなんだったの？という感じですがこれは歴史的経緯でこうなっています。
UWSCRではどうせならclassをもう少しそれっぽくしてみたいということで、仕様を大幅に変更しました。moduleはほぼそのままです。

# moduleの変更点

module構文はUWSCのそれとほぼ同等ですが、以下の変更点があります。

## どこにでも書ける

ユーザー定義関数と同様に、スクリプト下部に書かなければいけないという制限は撤廃されています。

## globalの拡張

特殊変数`global`が拡張されています。UWSCでは`global.関数名()`とすることでグローバル関数を明示的に呼び出すことができましたが、UWSCRでは変数や定数も呼び出せるようになっています。

```php
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では無名関数を使うことでプライベート関数を実現できます。

```php
module MyModule
    dim private = function()
        result = "private"
    fend
    
    function call_private()
        result = private()
    fend
endmodule

print MyModule.call_private() // private
print MyModule.private()      // エラーになる(※)
```

:::note warn
なんと執筆時点のバージョン`0.14.0`だとプライベート関数がエラーにならないというバグがありました！上記コードの※部分は*private*と出力されてしまいます。なんてこった…。
`0.15.0`までには修正します…！
:::

# classの変更点

class構文の記述方法には特に変更はありませんが、機能的には一切互換性がありません。moduleのような呼び出しが一切できなくなっていますが、代わりに`クラス名()`とすることでクラスのインスタンスを得られるようになっています。

## class定義

classを定義する場合はコンストラクタ(クラス名と同名の関数)が必須です。

```php
class MyClass
    dim name

    // コンストラクタ (必須)
    procedure MyClass(name: string)
        this.name = name
    fend
endclass
```

:::note info
moduleのコンストラクタはスクリプト開始時 (厳密にはmodule評価時) に実行されていましたが、classのコンストラクタはインスタンス作成時に実行されます。
:::

## インスタンスの作成

`クラス名()`というようにクラスを関数のように呼び出すとクラスインスタンスが返ります。このときコンストラクタが呼び出されます。

```php
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`に到達したとき
- スコープを抜ける際に、すべてのインスタンスがそのスコープのローカル変数だった場合
- スクリプト終了時

```php
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
```

:::note warn
…と書いたもののなんと執筆時点のバージョン`0.14.0`ではデストラクタが動作していませんでした！さらに`NOTHING`を代入してしまうとデッドロックします！ヤバイ！
これも`0.15.0`までに修正します。
:::


# 0.14.0で発生している数々のバグについて

バージョン`0.12.1`でmodule/classに関するバグを修正した際に、実はかなり大掛かりな変更が行われていました。そこで様々な不具合を含んでしまっていたのですが、その後の検証やテストが不十分であったためそれらが検知できていませんでした。このアドベントカレンダーの記事を書くに当たって記事内のコードは必ず動作確認をするのですが、そこで初めてこれらのバグが表面化しました。
これらは必ず次回リリース予定の`0.15.0`で修正します。この記事を読んでくださっている方にはご迷惑をおかけしますが、`0.14.0`でのサンプルコードの実行はお控えいただくようお願いいたします。