📜 ⬆️ ⬇️

Types and anonymous functions in PHP. Quack quack!

In this article I will not tell you what types are, I will not describe the syntax, or analyze all the subtleties associated with name resolution and inheritance of types. On this topic on Habré already have a fundamental article .
I just want to show one small, but proud example of using types together with anonymous functions. There will be nothing technically difficult in it: just one type and two classes. There is also not much practical value in it, as in any model example. But the idea - how to structure and reuse the code - in my opinion is very valuable.
Interested please under the cat.

Foreword


Somehow it happened that PHP (with the appearance of OOP in it) is very similar to Java in questions of structuring code. We inherit from the class, we implement the interfaces. We can even in the parameters of the methods indicate the need for the argument to belong to a specific family tree.

But, if in Java, as in a statically typed language, it makes sense, since it allows you to detect some circle of errors at the compilation stage, then what is the point in all these gestures in dynamically typed PHP? Maybe we worry too much about completely unnecessary things? Do we really need to inquire about the popes, grandmothers, or cousins ​​of the objects we received, when in essence we are only interested in whether the object can do what we need?

It is easy to see that I am talking about duck typing. A fairly powerful concept, following which you can write a very expressive code, and not necessarily prone to more errors and less stable than using the classical approach (you can google enough material on this topic, though mostly with reference to Ruby).
')
Some time ago anonymous functions appeared in PHP (5.3), and I thought: “Not bad! But not very useful. " Then (5.4) types appeared in PHP, and I realized that the time had come. Let's finally move on to the example and see what PHP has to offer.

Formulation of the problem


So, let's practice using types together with anonymous functions. What to train? Well, collections came to my mind, so we will train on them. First, think about what we want, and what are the ways to achieve this.

So, what actions can be performed on collections. Well, for example, we can find the maximum and minimum elements of a collection, or elements that satisfy a certain condition; we can get a new collection by applying some operation (map) on each element of the original collection, etc. ... The set of these methods is directly asked to be called a type .

What do we need from the collection so that we can implement these methods? Only one thing: we should be able to iterate over the elements of the collection. Let's call this necessary functionality a contract .

How can we implement this contract? There are two options:
  1. The standard approach is when we take an iteration over. Those. the client gets an iterator from the collection and uses it to bypass this collection.
  2. We assume that the collection knows better how to iterate on its own, and instead of rough walking through the collection , as in the previous version, we politely ask it to bypass itself , telling certain logic for iteration.

Both approaches are good (perhaps the first is a bit more flexible than the second), but I will choose approach number 2, because, firstly, it will serve my goals better, and secondly, because I wanted to.

Implementation

Before starting the example, I recommend you to download the code from the repository specially prepared for this case on GitHub, since The listings in the article will be truncated for clarity.
So, let's write our type, guided by the considerations described above.
Listing: Collections \ Enumerable
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; } /**     **/ } 


By itself, it does not carry much value, so we need a collection where it can be included. Let's implement this collection, using the example of a regular array:
Listing: Collections / 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); } } 


Now we have an array, it remains only to include the type Collections \ Enumerable and implement the contract:
 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); } } } 

As you can see, everything is rather trivial, but now we can do, for example, such things (and this required only a few lines of code from us):
 $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 

This, of course, is entertaining, but let's move on. What file, for example, is not a collection? The collection is of course, so nothing prevents us from doing, for example, like this:
 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); } } } 

We switched on the type, implemented the contract, and now we can, for example, calculate the total length of the lines of the file of odd length as follows (if we ever need it ^ _ ^):
 $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; }); 

These are the things here, gentlemen.

Conclusion


In my opinion, the use of types (or similar mechanisms) is more natural in dynamically typed languages ​​than dances with interfaces and inheritance. It gives us more flexibility and expressiveness, but isn't it why we came here for this? But each medal has a reverse side, and here this reverse side can be much more difficult for perception code, more confusing and implicit code. Remember, if something can be done, it is not necessary to do it. Big power - big responsibility, gentlemen!

Links


A post about the characters - habrahabr.ru/post/130000
About duck typing - en.wikipedia.org/wiki/Duck_typing
Repository with code from the article - github.com/ArtemPyanykh/php_fancy_collections

PS


If you have time, try to play around with the code. For example, implement the Collections \ Enumerable method for eachWithIndex, in the following format:
 $a->eachWithIndex(function($el, $idx) { echo $el; echo $idx; }); 

Or try to solve the problem of the local transition in the Collection \ Enumerable # find method (as soon as we find the first element that satisfies the condition, we can stop the iteration and return it).
If you come up with a good solution or just implement something interesting, please make a pull request.

Source: https://habr.com/ru/post/180333/


All Articles