0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PHPAdvent Calendar 2021

Day 15

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

Last updated at Posted at 2021-12-14

PHP Advent Calendar 2021 15日目

前日は@hanhan1978さんの複雑度の上がったLaravelをミドルウェアから眺める方法でした

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

【実装】イミュータブル

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

phpによる実装の前にnodejsdenoにおけるconstの確認から始めます

nodejsのconst

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

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を使う事で再現します

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を使う側
Interactive 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)を上書きしてはいけない

と言う制約はあるけどjavascriptconst的な感じになりました

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

普通っぽい感じで書くと

醜い
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 > $_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では、型が一発で定義出来ない場合にテンポラリー代わりの変数を無駄にスコープに追加しなきゃっていう場合もあるかもしれませんが、美しい書き方をすると美しい

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

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

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

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

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

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

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を使う側
Interactive 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 > 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 > 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に翻弄される
  • 最近まで技術に興味が無く好きでもないためこれまで一切勉強をした事が無かった、勉強を始めたのはここ1~2年
  • 現在フリーランス
  • 本日バースデイである
  • 今回初めてQiitaを書く
  • teratailでちらりちらりと回答をしている
  • nodejs必須のtypescriptは大嫌いですが、denoで動かすならtypescript好きです
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?