モナドをPHPに取り込む

http://hermetic.com/jones/in-operibus-sigillo-dei-aemeth/the-circumference-and-the-hieroglyphic-monad.html








ごく最近、私はいくつかの関数型言語とその概念で遊んで、関数型プログラミングのいくつかのアイデアが以前に書いたオブジェクトコードに適用できることに気付きました。 話す価値のあるアイデアの1つは、モナドです。 これは関数型言語のすべてのコーダーがチュートリアルを書こうとしているものです。これはクールですが理解しにくいことです。 この投稿はMonadsのチュートリアルではありません( このためにAveNatからのすばらしい翻訳があります )-むしろそれらをPHPでうまく使用する方法についての投稿です。



モナドとは何ですか?



上記の投稿を最後まで読むことができなかった場合(しかし無駄に!)、 Monadは状態の一種として表すことができ、異なるMonadがこの状態について異なることを行います。 しかし、それを読むことをお勧めします。 また、サンプルで使用されるため、GitHubのMonadPHPライブラリを少し使用したと仮定します。





最も単純なモナド-Identity Monadから始めましょう。 Monad基本クラスで定義されている関数は4つだけです。



namespace MonadPHP; class Identity { public function __construct($value) public function bind($function) public function extract() public static function unit($value) }
      
      





メソッドは4つしかなく、コンストラクタとバインドの2つだけが必要です。 他の2つは私たちの生活を大幅に簡素化しますが。



コンストラクターは新しいMonad(キャップ​​)を作成します-値を取得して保護されたプロパティに保存し、extractはその逆を行います。 これはMonadの標準機能ではありませんが、PHPはあまり機能的な言語ではないため追加しました。

静的ユニット関数は、単純なファクトリメソッドです。 入力パラメーターが現在のMonadかどうかを確認し、そうでない場合は新しいインスタンスを返します。

その結果、ここで私たちにとって最も価値のある方法はバインドです。 入力可能な呼び出し可能値を受け取り、Monadにある値を使用して呼び出します。 つまり、この関数はモナドで何が機能するのかさえ知らず、これがまさにアイデアの力全体が現れる場所です。



 use MonadPHP\Identity; $monad = Identity::unit(10); $newMonad = $monad->bind(function($value) { var_dump($value); return $value / 2; }); //  int(10) $b = $newMonad->extract(); var_dump($b); //  int(5)
      
      





すべてがシンプルです! そして無駄に。



ポイントは何ですか?



すべての力は何ですか? では、Monadを使って便利な変換を実行するために、バインド(またはその他の関数)するロジックを追加しましょう。



モナドは、nullから抽象化するために使用できます( 通常、同じ投稿を読む価値があるという理解がここにあります。これについては、今から行います.. )。 この場合、bindは、保存されているMonad値がnullでない場合にのみコールバックを呼び出します。 これにより、ネストされた条件からビジネスロジックが保存されるため、このコードをリファクタリングしてください。



 function getGrandParentName(Item $item) { return $item->getParent()->getParent()->getName(); }
      
      





クールですが、アイテムに親がない場合はどうなりますか( getParent()はnullを返します )? nullオブジェクトの呼び出しエラー(非オブジェクトのメンバー関数の呼び出し)が発生します。 この問題は次のように何らかの方法で解決できます。



 function getGrandParentName(Item $item) { if ($item->hasParent()) { $parent = $item->getParent(); if ($parent->hasParent()) { return $parent->getParent()->getName(); } } }
      
      





そして、Monadsを使用すると、次のようになります。



 function getGrandParentName($item) { $monad = new Maybe($item); $getParent = function($item) { //   null,     ! return $item->getParent(); }; $getName = function($item) { return $item->getName(); } return $monad ->bind($getParent) ->bind($getParent) ->bind($getName) ->extract(); }
      
      





はい、ここにはもう少しコードがありますが、変更点は次のとおりです。機能を段階的に手順ごとに増やすのではなく、状態を変更するだけです。 アイテムから始めて、親を選択し、再び親を選択して名前を取得します。 Monadsを介したこのような実装は、(親の名前を取得するための)タスクの本質の説明に近い一方で、一定のチェック/特定のない/危険についての考えを避けています。



別の例



値の配列でGrandParentNameを呼び出したい(値のリストから親の名前を取得したい)と仮定します。 または、それを繰り返して、毎回メソッドを呼び出すことができます。 しかし、これは回避できます。

ListMonadを使用すると、値の配列を1つとして置き換えることができます。 Monadを受け入れるように最後のメソッドを変更します。



 function getGrandParentName(Monad $item) { $getParent = function($item) { return $item->hasParent() ? $item->getParent() : null; }; $getName = function($item) { return $item->getName(); } return $item ->bind($getParent) ->bind($getParent) ->bind($getName); }
      
      





すべてがシンプルです。 これで、Maybe Monadを渡すことができ、getGrandParentNameは以前と同様に機能します。 値のリストを渡すことができるようになり、メソッドも引き続き機能します。 試してみましょう:



 $name = getGrandParentName(new Maybe($item))->extract(); // $monad = new ListMonad(array($item1, $item2, $item3)); //      Maybe Monad $maybeList = $monad->bind(Maybe::unit); $names = getGrandParentName($maybeList); // array('name1', 'name2', null)
      
      





繰り返しになりますが、すべてのビジネスロジックは同じままです。 すべての変更は外部から行われました。



主なアイデア



Monadsのおかげで、不必要なロジックから離れ、状態のロジックに集中できるという事実にあります。 手続き型スタイルで複雑なロジックを記述する代わりに、一連の単純な変換を行うことができます。 そして、異なるモナドで値に重みを付けることにより、通常のヌードルコードと同じロジックを実現できますが、何も複製する必要はありません。 ListMonadを思い出してください-オブジェクトの配列の操作を開始するためにメソッドを再定義する必要はありませんでした。



もちろん、これは万能薬ではなく、ほとんどのコードを単純化するものではありません。 しかし、この非常に興味深いアイデアには、OOPスタイルで記述するコードで多くの用途があります。 したがって、モナドで遊んで、モナドを作成し、モナドで実験してください!



upd:eld0727による現在の記事への追加- また、PHPのモナドについて



All Articles