PHPによる疑似的なイミュータブルとムーブセマンティクスモドキ

# [PHP Advent Calendar 2021](https://qiita.com/advent-calendar/2021/php)　15日目


https://qiita.com/advent-calendar/2021/php

前日は@hanhan1978さんの[複雑度の上がったLaravelをミドルウェアから眺める方法](https://qiita.com/hanhan1978/items/c572c1daa92f995f31e0)でした


早速本題というか軽い説明と実装に入ります


# 【実装】イミュータブル

イミュータブルとは不変である事を意味しますが、今回は**再束縛・再定義・再代入全ての意味で不可**という意味で使います
簡単に言うとjavascriptのconstです


phpによる実装の前に`nodejs`と`deno`における`const`の確認から始めます
### nodejsのconst

```js
Welcome to Node.js v17.2.0.
Type ".help" for more information.
> const varx =123;
undefined
> const varx =123;
Uncaught SyntaxError: Identifier 'varx' has already been declared
```
イミュータブルです


### denoのconst
```js
Deno 1.16.4
exit using ctrl+d or close()
> const verdeno = 123;
undefined
> const verx = verdeno;
undefined
> const verdeno = 345;
undefined
> verdeno
345
> verx
123
```
イミュータブルです

ですが最初に書いた通り今回はnodejs的な方でいきます

### phpによる再現

###### 1. イミュータブル再現の為のクラスを作る

phpにはconstによる変数定義が無いのでclassを使う事で再現します

```php:immutable.php
<?php
class Vars{
    
    private $vars = [];
    public function __set(string $name, $var){
        isset($this->vars[$name]) ? throw new Exception("\${$name} Already defined") : $this->vars[$name] = $var;
    }

    public function __get(string $name) {
        return $this->vars[$name];
    }
}
```

###### 2. Varsを使う側

```php:Interactive&nbsp;shell
Interactive shell

// $varsの定義
php > require_once('immutable.php');
php > $vars = new Vars();

// 通常の変数定義の代わりに、$varsのプロパティを定義する
php > $vars->input_text='onamax';
php > echo $vars->input_text;
onamax

// 再定義をしようとするとエラー
php > $vars->input_text='uwagaki';
Warning: Uncaught Exception: $input_text Already defined in /code/immutable.php:5
Stack trace:
#0 php shell code(1): Vars->__set('input_text', 'uwagaki')
#1 {main}
  thrown in /code/immutable.php on line 5

// 再定義が出来ないだけで、使用は可能
php > echo $vars->input_text;
onamax

// 別のプロパティ名なら当然定義可能
php > $vars->input_phone='09000000000';
php > echo $vars->input_phone;
09000000000
php > $vars->input_phone='09012341234';

Warning: Uncaught Exception: $input_phone Already defined in /code/immutable.php:5
Stack trace:
#0 php shell code(1): Vars->__set('input_phone', '09012341234')
#1 {main}
  thrown in /code/immutable.php on line 5
php > echo $vars->input_phone;
09000000000
```

- ソース中の変数は必ずVarsのプロパティとして持つ
- 一度しかVarsをnewしない
- Varsを保持してる変数（$vars）を上書きしてはいけない

と言う制約はあるけど`javascript`の`const`的な感じになりました

###では少し加工をしてから変数を持ちたい時はどうするか？

普通っぽい感じで書くと

```php:醜い
php > $_GET['birth'] = 2019; //例なのでこれはワザと
php > $vars = new Vars();
php > $vars->birth = $_GET['birth'];
php > if(20 <= 2021 - $vars->birth){
php {     $vars->over20 = true;
php { }else{
php {     $vars->over20 = false;
php { }
php > var_dump($vars->over20);
bool(false)
```

美しく書くと

```php:美しい
php > $_GET['birth'] = 2019;
php > $vars = new Vars();
php > $vars->birth = $_GET['birth'];
php > $vars->over20 = (function($birth) {
php (     if(20 <= 2021 - $birth){
php (         return true;
php (     }else{
php (         return false;
php (     }
php ( })($vars->birth);
php > var_dump($vars->over20);
bool(false)
```

何を言いたいかと言うと`$vars->over20`に値入れる部分が醜いよねという話です（主観なので醜いと感じるかは人それぞれです
**醜い例**の方で`if`の外でプロパティ定義しようと思っても無理です
例にしたコードが簡単過ぎて三項演算子で良いじゃんとかmatchで良いじゃんって思うかもしれませんが仕方ありません。短くて分かりやすい例が思い浮かばなかったのです。

`javascript`の場合だとこの為のだけに`const`ではなく`let`にする人もいるかもしれません
`typescript`では、型が一発で定義出来ない場合にテンポラリー代わりの変数を無駄にスコープに追加しなきゃっていう場合もあるかもしれませんが、美しい書き方をすると美しい

```typescript:ついでのtypescrpt&nbsp;on&nbsp;deno
const nu ="100";
const age: string = ((input)=> typeof input == "string" ? input: "cast:"+input.toString())(nu);
```



# 【実装】ムーブセマンティクス風

手抜き足抜きで言うとrustっぽい動きに見せるだけです

雑に分かりやすい言葉で言うと変数に入った値は一回しか使えないよっていうルールです
**変数に値を入れる → 変数を使う → その変数は消えてなくなる**という感じのやつです
なんで**風**かと言うと、ムーブしないからです
なので飽くまでムーブセマンティクス風な動きに見せるだけのものとなります


###### 1. ムーブセマンティクス風な動きの再現の為のクラスを作る

イミュータブルの例同様に、classを用意し`new Vars()`を使う事で何とかします

```php:move.php
<?php
class Vars{
    private $vars = [];
    public function __set(string $name, $var){
        $this->vars[$name] = $var;
    }

    public function __get(string $name) {
        if(isset($this->vars[$name])) {
            $_ = $this->vars[$name];
            unset($this->vars[$name]);
            return $_;
        } else {
            throw new Exception("Property \${$name} was moved(or undefined)");
        }
    }
    public function copy(string $name) {
        if(isset($this->vars[$name])) {
            return $this->vars[$name];
        } else {
            throw new Exception("Property \${$name} was moved(or undefined)");
        }
    }
}
```


###### 2. Varsを使う側

```php:Interactive&nbsp;shell
php > require_once('move.php');
php > $vars = new Vars();
php > $vars->name='oppai'; //プロパティをセット
php > echo($vars->name); //プロパティを使う
oppai
php > echo($vars->name); //プロパティを使う

Warning: Uncaught Exception: Property $name was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('name')
#1 {main}
  thrown in /code/move.php on line 14

php > $vars->name='oppai'; //プロパティを再度セット
php > echo($vars->name); //プロパティを使う
oppai
php > echo($vars->name); //プロパティを使う

Warning: Uncaught Exception: Property $name was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('name')
#1 {main}
  thrown in /code/move.php on line 14
```


- ソース中の変数は必ずVarsのプロパティとして持つ
- 一度しかVarsをnewしない
- Varsを保持してる変数（$vars）を上書きしてはいけない

とイミュータブルの例同様の制約があります

関数に渡す場合も普通に使うだけだと一度しか使えない

```php:一回しか使えない
php > function addAt($_) {
php {     echo "$_@";
php { }

php > $vars->lang = 'php';
php > addAt($vars->lang); //一回目は使えるが
php@
php > addAt($vars->lang); //二回目は使えない

Warning: Uncaught Exception: Property $lang was moved(or undefined) in /code/move.php:14
Stack trace:
#0 php shell code(1): Vars->__get('lang')
#1 {main}
  thrown in /code/move.php on line 14
```

なので値のコピーを渡して何度も使う

```php:一回しか使えない
php > function addAt($_) {
php {     echo "$_@";
php { }

php > $vars->lang = 'php';
php > addAt($vars->copy('lang')); //一回目使えて
php@
php > addAt($vars->copy('lang')); //二回目も使えて
php@
php > addAt($vars->copy('lang')); //二回目も使える
php@
```


----

以上です
どうでしたか？これを用いる事でバグ軽減の可能性を感じませんか？


# 自己紹介など

- 職業技術者歴十数年のphper、必要に応じてサーバーネットワーク触る、AWSに翻弄される
- 最近まで技術に興味が無く好きでもないためこれまで一切勉強をした事が無かった、勉強を始めたのはここ１～２年
- 現在フリーランス
- 本日バースデイである
- 今回初めてQiitaを書く
- teratailでちらりちらりと回答をしている
- nodejs必須のtypescriptは**大嫌い**ですが、denoで動かすならtypescript好きです