Функции высшего порядка — PHP: Функции
Пока мы рассмотрели только одну возможность объектов первого рода применительно к функциям — присвоение переменной. Самое интересное начинается, когда мы передаём одни функции в другие функции.
При таких условиях функция sort становится абсолютно бесполезной, потому что она может сортировать только списки примитивных типов данных. Но выше я описал только лишь одну из тысяч возможных ситуаций. Мы можем захотеть сортировать по любому параметру (или даже по набору параметров) и в любом порядке. Сортировки нужны часто, и многие из них довольно сложны. Худшее, что можно начать делать — реализовывать функцию sort под каждую ситуацию. Так что же делать? Если покопаться в документации PHP, то можно обнаружить функцию usort. Её определение звучит так:
Сортирует массив по значениям, используя пользовательскую функцию для сравнения элементов
Общая идея состоит в том, что нам не нужно реализовывать алгоритм сортировки каждый раз для каждой ситуации, ведь он не меняется. Все, что меняется — элементы, которые сравниваются между собой в процессе сортировки. И функция usort делегирует взаимодействие с этими элементами вызывающему коду.
Тогда код сокращается до такого:
Функция usort относится к так называемым функциям высшего порядка (high order functions). Функции высшего порядка — это функции, которые либо принимают, либо возвращают другие функции, либо делают все сразу. Такие функции, как правило, реализуют некий обобщённый алгоритм (например, сортировку), а ключевую часть логики делегируют вам через callback-функцию. Главный плюс от применения таких функций — серьёзное повышение коэффициента повторного использования кода.
В примере выше не обязательно создавать переменную для функции. Говоря откровенно, их вообще редко записывают в переменные. Типичное использование выглядит как прямая передача функции в функцию:
Потратьте немного времени, чтобы понять, где заканчивается одна функция и начинается другая. Подобный код мы начнём использовать уже со следующего урока.
Осталось рассмотреть то, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет.
Функциональное программирование на PHP
Что же это все-таки такое — функциональное программирование?
Прошло уже несколько лет с тех пор, как я начал использовать функциональные элементы в своем исходном коде, но я все еще не готов дать прямого и точного ответа на этот вопрос. И все же, несмотря на то, что у меня пока что нет четкого определения – я с уверенностью могу сказать, когда передо мной пример функционального программирования, а когда — нет. Поэтому я попробую зайти немного с другой стороны: функциональные программы обычно не прибегают к изменению состояний, а используют чистые функции. Эти чистые функции принимают значение и возвращают значение без изменения своего входного аргумента. Противоположный пример – это типичный сеттер в объектно-ориентированном контексте.
Типичный функциональный язык программирования поддерживает также функции высокого порядка – это функции, которые принимают в качестве аргументов или возвращают другие функции. Большинство из них поддерживает такие вещи, как карринг (currying) и частичное применение функции (partial function application). Также в языках функционального программирования можно встретить тщательно продуманную систему типов, которые используют option type для предотвращения появления нулевых указателей, которые стали обычным делом для императивных или объектно-ориентированных языков программирования.
Функциональное программирование обладает несколькими соблазнительными свойствами: отсутствие возни с состояниями делает параллелизм проще (но не простым – параллелизм никогда не бывает простым), фокусировка на функции — на минимальной единице кода, который можно было бы использовать снова – может привести к интересным вещам, связанным с их повторным использованием; требование к функциям быть определенными это отличная идея для создания стабильных программ.
Что может предложить PHP?
В PHP 5.4 появился новый тип “callable”, который позволяет простой доступ к мета-типу callable.
PHP в том числе поддерживает анонимные функции. Как упоминалось ранее, сообщество Haskell от души смеется над этим фактом, но главного все равно не отнять — мы наконец-то их получили. Да и шутки были вполне ожидаемы, потому что синтаксис выражений стал очень тяжелым. Возьмем простой пример на Python.
Симпатично, теперь взглянем на тот же код для Ruby:
Тоже неплохо, хоть нам и пришлось использовать блок и нестрогое лямбда-выражение. У Ruby тоже есть лямбда-выражения, но List.maphappens принимает блок, а не функцию. Перейдем к Scala:
Как видно из примеров, для строго типизированного языка программирования синтаксис всегда остается довольно компактен. Перепишем наш пример на PHP:
Ключевое слово function и отсутствие неявного return заставляют код выглядеть немного громоздким. Но, тем не менее, он работает. Еще один «строительный блок» в копилку для функционального программирования.
Кстати, array_map дает неплохой старт, но стоит учесть, что есть еще и array_reduce ; вот вам еще две важные функции.
Функциональный пример из реального мира
Давайте напишем простую программу, которая подсчитывает общую цену корзины покупок:
Да, это очень простой пример, который будет работать только для одного магазина. Но в нем используются не слишком сложные вычисления, и благодаря этому мы можем легко его переделывать, приводя к более функциональному стилю.
Давайте начнем с использования функций высшего порядка:
Теперь изменения состояний не происходит, даже внутри самой функции. array_map() возвращает новый массив из списка позиций в корзине с весом, налогом и стоимостью, а функция array_reduce собирает вместе массив итоговой суммы. Можем ли мы пойти дальше? Можем ли мы сделать программу еще проще?
Сразу возникает отличный контраргумент: правда ли, что пример стал проще для чтения? С первого взгляда — определенно нет, но со второго и дальше — вы привыкните. Лично у меня ушло какое-то время на то, чтобы привыкнуть к синтаксису Scala; сколько-то времени заняло изучение ООП и еще немало ушло на понимание функционального программирования. Это самая совершенная форма, в которую можно превратить исходный пример? Нет. Но при помощи этого кода вы увидели, насколько сильно меняется ваш подход к нему, когда вы мыслите в рамках применения функций к структурам данных, а не использования выражений вроде foreach для обработки структур данных.
Что еще можно сделать?
Вы когда-нибудь сталкивались с исключениями нулевого указателя (null pointer exceptions)? Существует такая вещь, как php-option, которая предоставляет нам реализацию полиморфического типа «возможно» (maybe) при помощи PHP-объекта.
Этому есть частичное применение: она превращает функцию, которая принимает n параметров, в функцию, которая принимает reactphp/curry (моя любимая реализация карринга для PHP):
Вот и все
Функциональное программирование это очень увлекательная тема; если бы меня спросили, что стало самой важной выученной мною вещью за последние годы, то я бы сразу ответил — знакомство с функциональными парадигмами. Оно настолько не похоже на все, что вы пробовали до этого, что ваш мозг будет болеть. Ну, в хорошем смысле.
И напоследок: почитайте «Функциональное программирование в реальном мире» (Real World Functional Programming). Там полным-полно хороших советов и примеров использования на практике.
Жду ваших замечаний и поправок к статье в личных сообщениях.
Функциональное программирование и PHP
Сегодня многие говорят о функциональном программировании, но не каждый сможет с уверенностью сказать, что следовал всем принципам данного способа написания программ. Главной причиной этому является наше однонаправленное мышление и обыкновенное незнание. В этой статье мы разберём основные принципы функционального программирования на PHP.
Важные аспекты функционального программирования
Начнём с терминов. Википедия определяет функциональное программирование как “парадигму программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних”. В данном способе программирования главным объектом манипуляции являются функции, в то время как в императивном программировании главным принципом является последовательное выполнение команд для достижения желаемого результата.
Когда я сказал, что главным объектом манипуляции являются функции, я имел в виду, что с ними мы можем делать всё, что угодно: передавать как аргументы в другие функции, определять функции в функциях, использовать функции в качестве возвращаемых значений! В общем, кругом одни функции.
Теперь давайте подойдём ближе к некоторым особенностями функционального программирования.
Неизменность
Свойством неизменности обладают значения, которые не могут быть изменены. В PHP и многих других языках их называют константами.
Рекурсия
Предположим, что мы хотим получить сумму элементов массива (забудьте о array_sum()). При функциональном подходе, решение данной задачи будет выглядеть так:
Если массив пустой, то сумма элементов будет равна 0. В случае если в массиве больше элементов, мы применяем рекурсию.
Чистые функции
Если функция никаким образом не меняет значения переменных, которые находятся за её пределами и не осуществляет действия по вводу/выводу данных в файл, базу данных и т.д., то такую функцию можно назвать чистой.
Функции высших порядков
Лямбда функции
Возможность создания подобных функций есть во множестве языков. Вы наверняка уже писали их в вашем JavaScript коде, когда реализовывали функции обратного действия при работе с ajax.
В PHP лямбда функции появились в версии 5.3:
Замыкания
Иногда всё же возникает необходимость иметь доступ к какой-то переменной, которая находится за пределами анонимной функции, но не является входящим параметром. Для того чтобы получить доступ к подобным переменным, можно воспользоваться замыканиями, используя ключевое слово use:
В этом случае, мы не передаём переменную как входящий параметр функции, а получаем к ней доступ, используя use.
Каррирование и частичное применение
В PHP мы можем создавать подобные функции при помощи замыканий. Ниже вы можете найти пример вычисления объёма коробки. Все аргументы являются необязательными. Однако, если передать меньше 3х параметров, то запустится ещё и другая внутренняя функция.
В первой проверке мы проверяем количество аргументов. Если оно равно 3, то возвращаем результат. Если изначальное количество аргументов меньше, вызываем другую функцию, которая пытается найти решение с заданными данными.
Таким образом, мы можем вычислить размер коробки, передав сначала один аргумент, а потом и другие.
Плюсы и минусы
В использовании функционального подхода есть множество плюсов. К примеру, анонимные функции можно использовать в качестве функций обратного вызова. Как в фрэймворке Slim:
В данном примере, анонимная функция запустится, когда пользователь зайдёт по адресу home.
При функциональном подходе нужно писать функции, которые выполняют какую-то свою узкопрофильную задачу, не затрагивающую какие-то глобальные вещи. Следуя данной парадигме, ваши программы не будут разбухать.
Однако стоит заметить, что не все приёмы данной парадигмы можно реализовать в PHP. В качестве примера, можно привести работу с параллельными процессами.
Также не всегда можно определить сложность рекурсивной функции и её эффект на производительность кода. Иногда стоит отойти от функционального принципа.
Наверное самым большим недостатком функционального подхода является то, что нужно отказаться от императивного мышления. Если это сделать, то новый подход к программированию может даже и увлечь.
Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: http://phpmaster.com/functional-programming-and-php/
Перевел: Станислав Протасевич
Урок создан: 29 Апреля 2013
Просмотров: 24411
Правила перепечатки
5 последних уроков рубрики «PHP»
Фильтрация данных с помощью zend-filter
Когда речь идёт о безопасности веб-сайта, то фраза «фильтруйте всё, экранируйте всё» всегда будет актуальна. Сегодня поговорим о фильтрации данных.
Контекстное экранирование с помощью zend-escaper
Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.
Подключение Zend модулей к Expressive
Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.
Совет: отправка информации в Google Analytics через API
Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.
Подборка PHP песочниц
Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.
Функциональное программирование можно определить, как парадигмы программирования, которые не меняют состояние программы, а вместо этого используют чистые функции. Чистая функция — это функция, которая может принимать значения и возвращать обработанные данные, не изменяя входящих данных.
Ее характерной особенностью является способность поддерживать функции более высокого порядка. Это значит, что эти функции принимают результаты вычислений от других функций и передают им данные после обработки.
Кроме того, функциональное программирование поддерживает системы, которые разработаны в естественной среде, что решает проблему нулевых указателей, которая является серьезным препятствием в объектно-ориентированном программировании.
Парадигма программирования заключается в том, что функционал имеет следующие атрибуты: не изменяет состояние, что упрощает осуществление распараллеливания; в основном взаимодействует с функциями, которые являются минимальными единицами кода, что делает весь код более читаемым; работает с детерминированными функциями, что способствует стабильности программы.
Как PHP реализует функциональное программирование
PHP имеет следующие характеристики, которые обеспечивают поддержку функционального программирования. А именно:
Анонимные функции или лямбда-функции
В основном они представляют собой классы, которые не содержат имен. Функция может работать с теми значениями, которые были заданы во время определения функции.
Замыкание
По определению замыкание похоже на анонимную функцию, однако разница заключается в том, что замыкание включает и некоторые части окружающего кода (расширенный диапазон действия).
С тех пор, как PHP использует по умолчанию раннее привязывание, значения из расширенного диапазона могут быть видны для функции через применение последующего привязывания.
Это осуществляется путем передачи по ссылке переменных из-за пределов диапазона, что реализуется через применение ключевых фраз.
Частичные функции и отделение
В РНР частичная функция является производной от общей функции, которая имеет множество переменных. Частичная функция имеет возможность фиксировать большинство переменных из общей функции.
Данное взаимодействие может быть реализовано с использованием замыкания следующим образом.
Отделение – это техника, которая принимает один аргумент, может быть создана функционалом, оперирующим несколькими аргументами. Например:
Отделение не является функцией PHP, но может быть создано ею.
Функции высшего порядка
Функции высшего порядка — это функции, которые способны принимать другие функции, в качестве входящих параметров. Количество параметров входящей функции должно быть тем же, что и у функции высшего порядка.
И точно то же количество параметров должно быть на выходе. Функции высшего порядка реализуются через замыкание и лямбда-функции.
Чистые функции, неизменные данные
Данный класс функций очень не просто реализовать через PHP. Чтобы сделать это нужно избегать изменений состояния и данных. В PHP чистые функции реализуются с помощью применения переменных, глобальных или статических, но эти переменные не изменяют входящие или исходящие данные. PHP также реализует неизменные данные, определяя свои переменные в качестве констант.
Рекурсия
Это техника программирования, которая подразумевает решение одной проблемы, исходя из решений других, более мелких проблем. Однако эти более мелкие проблемы должны быть того же характера, что и глобальная.
Рекурсии используется для того, чтобы поддерживать стабильное состояние системы и предотвратить изменение данных. Рекурсия реализуется не через структуру, а с помощью вызова функций.
Функциональное программирование имеет много преимуществ, которые решают проблемы программирования на PHP, однако не все они могут быть реализованы через PHP, так как функциональное программирование не является языком программирования, предназначенным для создания функций при проектировании.
Вот некоторые из этих преимуществ:
В заключение можно отметить, что функциональное программирование это не только парадигма программирования, поскольку оно рассматривает взаимодействие функций и их организацию без необходимости принятия конкретных шагов по их применению.
Для решения прикладных проблем требуется его реализация через отдельный язык программирования. Такой, например, как PHP.
Функции высших порядков и монады для PHP`шников
Среди PHP программ преобладает процедурный или в последних версиях частично объектно-ориентированный стиль программирования. Но можно писать и иначе, в связи с чем хочется рассказать о функциональном стиле, благо кое-какие инструменты для этого имеются и в PHP.
Поэтому мы рассмотрим реализацию парсера JSON в виде простейших функций и функций их комбинирующих в более сложные, постепенно дойдя до полноценного парсера JSON формата. Вот пример кода, который мы получим:
Кроме собственно функционального подхода можно обратить внимание на использование классов для создания DSL-подобного синтаксиса и на использование генераторов для упрощения синтаксиса комбинаторов.
UPDATE само-собой парсинг JSON уже давно решенная задача и конечно готовая и протестированная функция на C будет работать лучше. Статья использует эту задачу как пример для объяснения функционального подхода. Так же не пропагандируется использование именно такого кода в продакшене, каждый может почерпнуть себе какие-то идеи, которые могут упростить код и жизнь.
Полный код находится на github.
Функциональный стиль
Как программист справляется с огромной сложностью программ? Он берет простые блоки и строит из них более сложные, из которых строятся еще более сложные блоки и в конце-концов программа. По крайней мере так было после появляния первых языков с подпрограммами.
В основе процедурного стиля лежит описание процедур, которые вызывают другие процедуры вместе меняющие какие-то общие данные. Объекто-ориентированный стиль добавляет возможность описывать структуры данных, составленные из других структур данных. Функциональный же стиль использует композицию (соединение) функций.
Чем же отличается композиция функций от композиции процедур и объектов? Основа функционального подхода — чистота функций, это значит, что результат работы функций зависит только от входных параметров. Если функции чисты, то гораздо проще предсказать результат их композиции и даже создать готовые функции для преобразования других функций.
Именно функции принимающие и/или возвращающие в качестве результата другие функции называются функциями высшего порядка и представляют тему этой статьи.
Какую задачу будем решать?
Для примера возьмем задачу, которую не очень просто решить целиком и посмотрим как функциональный подход поможет нам эту задачу упростить.
Для примера попробуем сделать парсер формата JSON, который из строки JSON получит соответствующий PHP объект: число, строку, список или ассоциированный список (со всеми возможными вложенными значениями конечно).
Что такое парсер?
Начнем с самых простых элементов: мы пишем парсер, что же это такое? Парсер это функция, которая берет строку и в случае успеха возвращает пару значений: результат разбора и остаток строки (если разбираемое значение занимало не всю строку) или пустой набор, если разобрать строку не удалось:
Например если у нас есть функция-парсер number то мы могли бы написать такие тесты:
DISCLAMER: PHP имеет не слишком удобный синтаксис для работы с функциями, поэтому для более простого и понятного кода мы будем использовать класс, который является ни чем иным, как просто оберткой вокруг функции парсера и нужен для указания типов и для использования удобного синтаксиса цепочных вызовов, о чем подробнее поговорим дальше.
Простейшие парсеры
Полезность этой функции не очевидна, давайте сделаем ее более общей и объявим функцию, которая позволит создавать подобный парсер для любого значения:
Пока все просто, но все еще не очень полезно, ведь мы хотим парсить строку, а не возвращать всегда одно и то же. Давайте сделаем парсер, который возвращает первые несколько символов входной строки:
Уже лучше, первый наш парсер, который действительно разбирает строку! Напомню: мы описываем простейшие кирпичики из который сможем собрать более сложный парсер. Поэтому для полноты картины нам нехватает только парсера, который ничего не парсит вообще.
Он нам еще пригодится.
Вот и все парсеры, которые нам нужны. Этого достаточно, чтобы разобрать JSON. Не верите? Осталось придумать способ собирать эти кирпичики в более сложные блоки.
Собираем кирпичики вместе
Поскольку мы решили заняться функциональным программированием, то для комбинирования функций парсеров в более сложные парсеры логично использовать функции!
Например если у нас есть парсеры first и second и мы хотим применить к строке любой из них мы можем определить комбинатор парсеров — функцию, создающую новый парсер на основе существующих:
Но, как уже упоминалось выше, такой синтаксис может быстро стать нечитаемым (например oneOf($a,oneOf($b,oneOf($c,$d))) ), поэтому мы перепишем эту (и все следующие) функции как методы класса Parser :
И еще одна, не такая простая, но зато гораздо более мощная функция:
Основная особенность проделанной работы состоит в том, что мы описываем, что делать с результатами парсинга, но сама разбираемая строка тут не участвует, у нас нет возможности ошибиться и тем, какую часть строки куда передавать. А дальше мы увидим как сделать синтаксис таких комбинаций более простым и читабельным.
Эта функция позволит нам описать несколько других полезных комбинаторов:
Этот комбинатор позволяет уточнить действие парсера и проверить его результат на соответствие какому-то критерию. Например с его помощью мы постром очень полезный парсер:
DO нотация
Теперь мы приступим к построению более сложных парсеров, но перед этим хотелось бы сделать способ их описания более простым: комбинирующая функция flatMap позволяет нам многое, но выглядит это не очень.
В связи с этим подсмотрим как решают эту проблему другие языки. Так в языках Haskell и Scala есть весьма удобный синтаксис для работы с подобными вещами (они даже имеют свое название — монады), называется он (в Haskell) DO-нотация.
Генераторы
Давайте сделаем небольшое отступление и рассмотрим что такое генераторы. В PHP 5.5.0 и выше появилась возможность описать функцию:
Это можно использовать, чтобы спрятать вызовы flatMap от программиста.
flatMap используя комбинаторы
То же самое без рекурсии и функции flatMap можно было бы записать так:
Объекты, которые можно комбинировать с помощью двух методов flatMap и just называют монады (это немного упрощенное определение) и этот же код можно использовать, чтобы писать комбинаторы для промисов (Promise), опциональных значений (Maybe,Option) и многих других.
Но ради чего же мы писали эту не самую простую функцию? Для того, чтобы дальнейшее использование flatMap было гораздо легче. Сравним один и тот же код с чистым flatMap :
и тот же самый код, но написанный через _do :
Результирующий парсер делает то же самое тем же способом, но читать и писать такой код гораздо проще!
Строим более сложные парсеры и комбинаторы
Теперь, используя эту нотацию мы можем написать еще несколько полезных парсеров:
И полезные методы класса Parser для повторяющихся элементов:
jNumber = (‘-‘|’+’|») 3+ (.7+)?
Код вполне самодокументирующийся, читать и искать в нем ошибки довольно просто.
jBool = true | false
jString = ‘»‘ [^»]* ‘»‘
jList = ‘[‘ (jValue (, jValue)*)? ‘]’
jObject = ‘<' (pair (, pair)*)? '>‘
jValue = jNull | jBool | jNumber | jString | jList | jObject
Заключение
Моей целью было конечно не написание очередного парсера JSON, а демонстрация того, как написание маленьких простых (и функционально чистых) функций, а так же простых способов их комбинирования позволяет строить сложные функции простым способом.
А в простом и понятном коде и ошибок меньше. Не бойтесь функционального подхода.