- PVSM.RU - https://www.pvsm.ru -
PHP всегда был простым, процедурным языком программирования, черпавшим свое вдохновение из C и Perl. В PHP 5 появилась правильная объектная модель, но о ней вы уже все знаете. А вот в PHP 5.3 появились замыкания (closure), которые были серьезно улучшены в версии 5.4 (подсказка: $this
теперь доступен по умолчанию).
Прошло уже несколько лет с тех пор, как я начал использовать функциональные элементы в своем исходном коде, но я все еще не готов дать прямого и точного ответа на этот вопрос. И все же, несмотря на то, что у меня пока что нет четкого определения – я с уверенностью могу сказать, когда передо мной пример функционального программирования, а когда — нет. Поэтому я попробую зайти немного с другой стороны: функциональные программы обычно не прибегают к изменению состояний, а используют чистые функции. Эти чистые функции принимают значение и возвращают значение без изменения своего входного аргумента. Противоположный пример – это типичный сеттер в объектно-ориентированном контексте.
Типичный функциональный язык программирования поддерживает также функции высокого порядка – это функции, которые принимают в качестве аргументов или возвращают другие функции. Большинство из них поддерживает такие вещи, как карринг (currying) и частичное применение функции (partial function application). Также в языках функционального программирования можно встретить тщательно продуманную систему типов, которые используют option type [1] для предотвращения появления нулевых указателей, которые стали обычным делом для императивных или объектно-ориентированных языков программирования.
Функциональное программирование обладает несколькими соблазнительными свойствами: отсутствие возни с состояниями делает параллелизм проще (но не простым – параллелизм никогда не бывает простым), фокусировка на функции — на минимальной единице кода, который можно было бы использовать снова – может привести к интересным вещам, связанным с их повторным использованием; требование к функциям быть определенными это отличная идея для создания стабильных программ.
PHP не является «настоящим» или «чистым» функциональным языком. Он далек от этого. Здесь нет надлежащей системы типов, "крутые пацаны [2]" катаются со смеху от нашего экзотического синтаксиса для замыканий, а еще тут есть функция array_walk()
, которая на первый взгляд выглядит функциональной, но позволяет изменение состояний.
Тем не менее, здесь есть несколько интересных «строительных блоков» для целей функционального программирования. Для начала, возьмем call_user_func
, call_user_func_array
и $callable()
. call_user_func
принимает callback-функцию и список аргументов, после чего вызывает этот callback с переданными аргументами. call_user_func_array
делает то же самое, за исключением того, что она принимает массив аргументов. Это очень похоже на fn.call()
и fn.apply()
в JavaScript (без передачи области видимости). Гораздо менее известная, но отличная функция в PHP 5.4 это возможность вызывать функции. callable
это мета-тип в PHP (то есть состоящий из нескольких вложенных типов): callable
может быть строкой для вызова простых функций, массивом из <string,string>
для вызова статичных методов и массивом из <object,string>
для вызова методов объекта, экземпляра Closure
или чего угодно, осуществляющего магический метод __invoke()
, также известный как Функтор. Это выглядит примерно следующим образом:
$print = 'printf';
$print("Hello %sn", 'World');
В PHP 5.4 появился новый тип “callable”, который позволяет простой доступ к мета-типу callable.
PHP в том числе поддерживает анонимные функции. Как упоминалось ранее, сообщество Haskell от души смеется над этим фактом, но главного все равно не отнять — мы наконец-то их получили. Да и шутки были вполне ожидаемы, потому что синтаксис выражений стал очень тяжелым. Возьмем простой пример на Python.
map(lambda v: v * 2, [1, 2, 3])
Симпатично, теперь взглянем на тот же код для Ruby:
[1, 2, 3].map{|x| x * 2}
Тоже неплохо, хоть нам и пришлось использовать блок и нестрогое лямбда-выражение. У Ruby тоже есть лямбда-выражения, но List.maphappens принимает блок, а не функцию. Перейдем к Scala:
List(1, 2, 3).map((x: Int) => x * 2)
Как видно из примеров, для строго типизированного языка программирования синтаксис всегда остается довольно компактен. Перепишем наш пример на PHP:
array_map(function ($x) {return $x * 2;}, [1, 2, 3]);
Ключевое слово function
и отсутствие неявного return
заставляют код выглядеть немного громоздким. Но, тем не менее, он работает. Еще один «строительный блок» в копилку для функционального программирования.
Кстати, array_map
дает неплохой старт, но стоит учесть, что есть еще и array_reduce
; вот вам еще две важные функции.
Давайте напишем простую программу, которая подсчитывает общую цену корзины покупок:
$cart = [
[
'name' => 'Item 1',
'quantity' => 10,
'price' => 9.99,
],
[
'name' => 'Item 2',
'quantity' => 3,
'price' => 5.99,
]
];
function calculate_totals(array $cart, $vatPercentage)
{
$totals = [
'gross' => 0,
'tax' => 0,
'net' => 0,
];
foreach ($carts as $position) {
$sum = $position['price'] * $position['quantity'];
$tax = $sum / (100 + $vatPercentage) * $vatPercentage;
$totals['gross'] += $sum
$totals['tax'] += $tax
$totals['net'] += $sum - $tax;
}
return $totals;
}
calculate_totals($cart, 19);
Да, это очень простой пример, который будет работать только для одного магазина. Но в нем используются не слишком сложные вычисления, и благодаря этому мы можем легко его переделывать, приводя к более функциональному стилю.
Давайте начнем с использования функций высшего порядка:
$cart = [
[
'name' => 'Item 1',
'quantity' => 10,
'price' => 9.99,
],
[
'name' => 'Item 2',
'quantity' => 3,
'price' => 5.99,
]
];
function calculate_totals(array $cart, $vatPercentage)
{
$cartWithAmounts = array_map(
function (array $position) use ($vatPercentage) {
$sum = $position['price'] * $position['quantity'];
$position['gross'] = $sum;
$position['tax'] = $sum / (100 + $vatPercentage) * $vatPercentage;
$position['net'] = $sum - $position['tax'];
return $position;
},
$cart
);
return array_reduce(
$cartWithAmounts,
function ($totals, $position) {
$totals['gross'] += $position['gross'];
$totals['net'] += $position['net'];
$totals['tax'] += $position['tax'];
return $totals;
},
[
'gross' => 0,
'tax' => 0,
'net' => 0,
]
);
}
calculate_totals($cart, 19);
Теперь изменения состояний не происходит, даже внутри самой функции. array_map()
возвращает новый массив из списка позиций в корзине с весом, налогом и стоимостью, а функция array_reduce
собирает вместе массив итоговой суммы. Можем ли мы пойти дальше? Можем ли мы сделать программу еще проще?
А что, если мы разобьем программу на части еще меньше и посмотрим, что она делает на самом деле:
Теперь нам потребуется маленький помощник. Этим маленьким помощником нам станет functional-php [3], небольшая библиотека функциональных примитивов, которую я разрабатываю уже несколько лет. Для начала, тут есть Functionalpluck()
, которая делает то же самое, что и _.pluck()
из underscore.js
. Другая полезная функция оттуда — это Functionalzip()
. Она «сжимает» вместе два списка, опционально используя callback-функцию. Functionalsum()
суммирует элементы списка.
use Functional as F;
$cart = [
[
'name' => 'Item 1',
'quantity' => 10,
'price' => 9.99,
],
[
'name' => 'Item 2',
'quantity' => 3,
'price' => 5.99,
]
];
function calculate_totals(array $cart, $vatPercentage)
{
$gross = Fsum(
Fzip(
Fpluck($cart, 'price'),
Fpluck($cart, 'quantity'),
function($price, $quantity) {
return $price * $quantity;
}
)
);
$tax = $gross / (100 + $vatPercentage) * $vatPercentage;
return [
'gross' => $gross,
'tax' => $tax,
'net' => $gross - $tax,
];
}
calculate_totals($cart, 19);
Сразу возникает отличный контраргумент: правда ли, что пример стал проще для чтения? С первого взгляда — определенно нет, но со второго и дальше — вы привыкните. Лично у меня ушло какое-то время на то, чтобы привыкнуть к синтаксису Scala; сколько-то времени заняло изучение ООП и еще немало ушло на понимание функционального программирования. Это самая совершенная форма, в которую можно превратить исходный пример? Нет. Но при помощи этого кода вы увидели, насколько сильно меняется ваш подход к нему, когда вы мыслите в рамках применения функций к структурам данных, а не использования выражений вроде foreach
для обработки структур данных.
Вы когда-нибудь сталкивались с исключениями нулевого указателя (null pointer exceptions)? Существует такая вещь, как php-option [4], которая предоставляет нам реализацию полиморфического типа «возможно» (maybe) при помощи PHP-объекта.
Этому есть частичное применение: она превращает функцию, которая принимает n параметров, в функцию, которая принимает <n параметров. Чем это может быть полезно? Возьмем извлечение первого символа из списка строк.
Скучный путь:
$list = ['foo', 'bar', 'baz'];
$firstChars = [];
foreach ($list as $str) {
$firstChars[] = substr($str, 0, 1);
}
Функциональный путь без PFA (частичного применения функций):
array_map(function ($str) {return substr($str, 0, 1);}, ['foo', 'bar', 'baz']);
Путь с PFA и с использованием reactphp/curry
(моя любимая реализация карринга для PHP):
use ReactCurry;
array_map(Currybind('strpos', Curry…(), 0, 1), ['foo', 'bar', 'baz']);
Да. … (HORIZONTAL ELLIPSIS, U+2026)
это корректное имя функции в PHP. Но если оно вам по какой-то причине не сильно приглянулось, можете использовать вместо него useCurryplaceholder()
.
Функциональное программирование это очень увлекательная тема; если бы меня спросили, что стало самой важной выученной мною вещью за последние годы, то я бы сразу ответил — знакомство с функциональными парадигмами. Оно настолько не похоже на все, что вы пробовали до этого, что ваш
И напоследок: почитайте «Функциональное программирование в реальном мире [6]» (Real World Functional Programming). Там полным-полно хороших советов и примеров использования на практике.
P.S. Спасибо этому посту [7], который утвердил меня в ценности статьи для Хабра и вдохновил на завершение ее перевода.
Также жду ваших замечаний и поправок к статье в личных сообщениях.
Автор: HotWaterMusic
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/25772
Ссылки в тексте:
[1] option type: http://en.wikipedia.org/wiki/Option_type
[2] крутые пацаны: http://haskell.org/
[3] functional-php: https://github.com/lstrojny/functional-php
[4] php-option: https://github.com/schmittjoh/php-option
[5] мозг: http://www.braintools.ru
[6] Функциональное программирование в реальном мире: http://www.manning.com/petricek/
[7] этому посту: http://habrahabr.ru/company/zfort/blog/167127/
[8] Источник: http://habrahabr.ru/post/167181/
Нажмите здесь для печати.