PHPの特性と匿名関数。 いんちき!

この記事では、トレイトとは何かを説明しません。構文については説明しません。また、名前解決とトレイトの継承に関連するすべての微妙な点を分析しません。 Habréには、このトピックに関する基本的な記事がすでにあります。

私は、匿名関数と組み合わせて特性を使用する小さな、しかし誇らしい例を示したいと思います。 技術的に複雑なものではありません。1つのタイプと2つのクラスだけです。 また、モデルの例のように、その実用的な価値もあまり高くありません。 しかし、アイデア-コードの構造化と再利用の方法-は私の意見では非常に価値があります。

猫の下で興味を持ってください。



まえがき



どういうわけか、PHP(OOPの出現)がコード構造の観点から見た場合、Javaと非常に似ていることが判明しました。 クラスから継承し、インターフェイスを実装します。 メソッドのパラメーターで、引数が特定の家系図に属するかどうかを示すこともできます。



しかし、静的に型付けされた言語のようにJavaの場合、コンパイル段階で特定の範囲のエラーを識別できるため、これは理にかなっています。動的に型付けされたPHPのこれらすべてのジェスチャーのポイントは何ですか? 完全に不要なものを心配しすぎているのではないでしょうか? オブジェクトが必要なことを実行できるかどうかだけに関心がある場合、受け取ったオブジェクトの父、祖母、またはいとこについて問い合わせるのは本当に必要ですか?



私がアヒルのタイピングについて話しているのは簡単にわかります。 非常に表現力豊かなコードを書くことができるかなり強力な概念であり、必ずしも古典的なアプローチを使用するよりも多くのエラーや安定性の低下を被ることはありません(主にRubyに関してですが、このトピックに関する十分な資料をGoogleで検索できます)。



しばらく前に、匿名関数(5.3)がPHPに登場し、「悪くない! しかし、あまり有用ではありません。」 それから(5.4)PHPに特徴が現れ、私は時が来たことに気付きました。 最後に例に移り、PHPが提供するものを見てみましょう。



問題の声明



それでは、匿名関数と組み合わせて特性を使用して練習しましょう。 何を訓練しますか? さて、コレクションは私の頭に浮かんだので、それらについてトレーニングします。 まず、私たちが何を望み、これを達成する方法について考えてみましょう。



そのため、コレクションに対して実行できるアクション。 たとえば、コレクションの最大要素と最小要素、または特定の条件を満たす要素を見つけることができます。 元のコレクションの各要素に何らかの操作(マップ)を適用するなどして、新しいコレクションを取得できます。これらのメソッドのセットは、 typeと呼ばれるように直接要求されます。



これらのメソッドを実装するには、コレクションから何が必要ですか? 唯一のことです。コレクションの要素を反復処理できる必要があります。 この基本的な機能をコントラクトと呼びましょう。



この契約をどのように実装できますか? 次の2つのオプションがあります。

  1. 自分自身でイテレーションを行うときの標準的なアプローチ。 つまり クライアントはコレクションからイテレータを受け取り、それを使用してこのコレクションをバイパスします。
  2. コレクションはそれ自体で反復する方法をよりよく知っていると仮定し、以前のバージョンのようにコレクションを大まか歩く代わりに、反復のために特定のロジックを伝えて、それ自体を移動するよう丁寧に依頼します。


どちらのアプローチも優れています(おそらく最初のアプローチは2番目のアプローチよりも多少柔軟性があります)が、アプローチ番号2を選択します。



実装

例に着手する前に、GitHubでこのケースのために特別に準備されたリポジトリからコードをダウンロードすることをお勧めします。 記事のリストは、明確にするためにトリミングされます。
それでは、上記の考慮事項に沿って、タイプを書きましょう。

リスト:コレクション\列挙可能
namespace Collections; trait Enumerable { /**     ,  $block     **/ abstract public function each(\Closure $block); public function findAll(\Closure $predicate) { $result = new FancyArray(); $this->each(function($el) use ($predicate, &$result) { if ($predicate($el)) { $result[] = $el; } }); return $result; } public function map(\Closure $block) { $result = new FancyArray(); $this->each(function($el) use ($block, &$result) { $processed = $block($el); $result[] = $processed; }); return $result; } /**     **/ }
      
      







それ自体ではあまり価値がないので、それを含めることができるコレクションが必要です。 例として通常の配列を使用して、このコレクションを実装しましょう。

リスト:コレクション/ FancyArray
 namespace Collections; class FancyArray implements \ArrayAccess, \Countable { protected $container; function __construct($initial = array()) { if (is_array($initial)) { $this->container = $initial; } else if ($initial instanceof FancyArray) { $this->container = $initial->toArray(); } } public function offsetExists($offset) { return isset($this->container[$offset]); } public function offsetGet($offset) { return isset($this->container[$offset]) ? $this->container[$offset] : null; } public function offsetSet($offset, $value) { if (is_null($offset)) { $this->container[] = $value; } else { $this->container[$offset] = $value; } } public function offsetUnset($offset) { unset($this->container[$offset]); } public function toArray() { return $this->container; } public function count() { return count($this->container); } }
      
      







これで配列ができました。CollectionsEnumerableの型を含めて、コントラクトを実装するだけです。

 namespace Collections; class FancyArray implements \ArrayAccess, \Countable { use Enumerable; ... ... ... ... /** * Calls $block for every element of a collection * @param callable $block */ public function each(\Closure $block) { foreach ($this->container as $el) { $block($el); } } }
      
      





ご覧のとおり、すべてが非常に簡単ですが、たとえば、次のようなことができるようになりました(これにより、ほんの数行のコードが必要になりました)。

 $a = new FancyArray([1, 2, 3, 4]); $res = $a->map(function($el) { return $el*$el; }); // [1, 4, 9, 16] $res->reduce(0, function($initial, $el) { return $initial + $el; })); // 30
      
      





もちろん、これは面白いですが、先に進みましょう。 たとえば、ファイルではなく、コレクションですか? コレクションはもちろんですので、たとえば次のようにすることを妨げるものはありません。

 namespace IO; class FancyFile extends \SplFileObject { use \Collections\Enumerable; public function each(\Closure $block) { $this->fseek(0); while ($this->valid()) { $line = $this->fgets(); $block($line); } } }
      
      





トレイトをオンにし、コントラクトを実装しました。これで、たとえば、次のように奇数長ファイルの行の合計長を計算できます(^ _ ^が必要な場合)。

 $obj = new FancyFile(<filename>); $res = $obj ->select(function($el) { return strlen(trim($el)) % 2 == 1; }) ->map(function($el) { return strlen(trim($el)); }) ->reduce(0, function($initial, $el) { return $initial + $el; });
      
      





これらは、紳士です。



おわりに



私の意見では、動的に型付けされた言語では、インターフェイス(継承)を使用したダンスよりも、特性(または同様のメカニズム)の使用がより自然です。 これは私たちに大きな柔軟性と表現力を与えますが、それが私たちがここに来た理由ではありませんか? しかし、各コインには裏返しがあり、この裏返しはコードを読むのがはるかに難しく、混乱を招き、暗示的なコードになります。 何かを行うことができる場合、それを行う必要がないことを忘れないでください。 偉大な力-偉大な責任、紳士!



参照資料



タイプに関する投稿-habrahabr.ru/post/130000

アヒルのタイピングについて-ja.wikipedia.org/wiki/Duck_typing

記事のコードを含むリポジトリ-github.com/ArtemPyanykh/php_fancy_collections



PS



時間があれば、コードで遊んでみてください。 たとえば、次の形式のCollections \ EnumerableメソッドeachWithIndexを実装します。

 $a->eachWithIndex(function($el, $idx) { echo $el; echo $idx; });
      
      





または、Collection \ Enumerable#findメソッドで非ローカル遷移の問題を解決しようとします(条件を満たす最初の要素が見つかったら、繰り返しを停止して返すことができます)。

良い解決策を思いつくか、何か面白いものを実装する場合は、プルリクエストを行ってください。



All Articles