Функциональное программирование и PHP






Масса разработчиков программного обеспечения любят поговорить о функциональном программировании, однако если вы спросите их о том, применяли ли они его где-нибудь, в большинстве случаев в ответ вы получите «нет». Причина такой ситуации довольно проста. Когда мы учились программировать, разглядывая блок-схемы и размышляя о том, что и на каком шаге должна делать программа, мы выработали привычку мыслить императивно. В этой статье автор поделится с вами основными принципами, лежащими в основе функционального программирования, а также способами его применения в PHP.

PHP

Основные понятия функционального программирования

Начнём с определения. Википедия сообщает, что

«Функциональное программирование — раздел дискретной математики и парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании).»

В функциональном программировании функции являются объектами первого класса (first-class citizen), в то время как в императивном программировании мы имеем дело в основном с переменными и пошаговым изменением их значений с целью получить требуемое состояние системы.

Когда говорят, что функция является «объектом первого класса», это означает что мы можем использовать функцию точно таим же образом, как мы используем переменную в императивном программировании. То есть, функции можно передавать в качестве параметров при вызове других функций, объявлять внутри других функций и даже возвращать функции в качестве значения других функций. Если попытаться выразиться короче, то «функция — это значение».

О'кей, вернёмся к этому позже, а пока что рассмотрим несколько ключевых понятий функционального программирования.

Неизменность

Неизменность — это поведение, при котором значение переменной не моет быть изменено после того, как оно определено. В разных языках это реализуется по-разному, а в PHP в частности для этого используются константы.

Рекурсия

Рекурсия — частое явление в функциональном программировании. В императивном программировании мы можем пользоваться циклами всякий раз, когда мы имеем дело с коллекциями или массивами, перебирая элементы и пользуясь временной переменной, чтобы сохранять промежуточные значения. Но при функциональном подходе такое сделать не получится по причине наличия принципа неизменности. Здесь на помощь придут рекурсия и стек вызова функций.

Допустим, нам потребовалось создать функцию, вычисляющую сумму всех элементов массива (все дружно делаем вид, что array_sum () не существует). Следуя духу функционального программирования, наша реализация выглядела бы примерно так:

Чистые функции и ссылочная прозрачность

Если функция не изменяет внешних по отношению к ней объектов, в том числе и не выполняет никаких операций ввода-вывода (в файл, базу данных, etc), то такую функцию называют «функцией без побочных эффектов» или «чистой функцией». Так, например, все математические функции являются чистыми, в то время как функции вроде date () и rand () — нет.

Значение, возвращаемое чистой функцией, будет всегда одинаково для одного и того же набора аргументов. Это приводит к следующему свойству, называемому «ссылочная прозрачность». Если функция «ссылочно-прозрачна», то мы можем заменить её, на возвращаемое ею значение, и это никак не отразиться на работе программы.

Функции высшего порядка

Концепции, описанные выше, могут быть реализованы практически в любом языке программирования, но чистые функции и функции высшего порядка — это два момента, отличительные для функциональных языков. Выше автор объяснил, что функции первого класса могут интерпретироваться в программе наравне со значениями. Функции же высшего порядка, это такие функции, которые могут принимать другие функции в качестве параметров вызова и возвращать функции в качестве значений. Относительно недавно в PHP были добавлены новые возможности, позволяющие создавать функции высшего порядка: лямбда-функции и замыкания.

Лямбда-функции

Лямбда-функция (также известная как анонимная функция) — это функция, у которой нет имени. Когда объявляется лямбда-функция, вы получаете ссылку на неё, которую можно сохранить в переменной с целью дальнейшего использования. То есть, вы можете использовать эту переменную везде, где вам потребуется вызов функции.

Такая возможность существует во многих языках. Одним из знакомых примеров для вас, скорее всего, окажется JavaScript, в котором лямбда-функции обычно используются в качестве callback-обработчиков событий.

PHP обзавёлся лямбда-функциями с выходом версии 5.3, и это предоставило возможность разработчиком создавать конструкции вида:

Говоря о функциях, а особенно о лямбда-функциях, важно понимать, как функционирует область видимости переменных. Например, JavaScript, позволяет вам обращаться во внешнюю по отношению к лямбда-функции область видимости, в то время как PHP этого не даст сделать. Внутри лямбда-функции в PHP вы можете получить доступ только области видимости функции. То есть, в этом аспекте своей работы лямбда-функции ничем не отличаются от своих «традиционных» собратьев.

Замыкания

Иногда возникает необходимость обратиться к переменной, находящейся в родительской, по отношению к функции, области видимости. Замыкания — это почти то же самое, что и лямбда-функции с той лишь разницей, что изнутри замыканий вы можете обращаться к переменным, находящимся во внешней области видимости. Для того, чтобы сделать это, вам понадобиться ключевой слово use, появившееся также в версии PHP 5.3.

Частичные функции и карринг

В двух словах, частичная функция — это функция, создана на основе существующей функции и части её аргументов. В PHP частичные функции мы можем создавать при помощи замыканий.  В примере ниже представлено вычисление объёма параллелепипеда на основе длины, высоты и ширины, передаваемых через аргументы функции. Все аргументы являются необязательными. Решение достигается за счёт того, что в случае недостатка одного или более аргументов, будет создана функция, принимающая недостающие значения.

Все аргументы являются необязательными. Сначала выполняется проверка, переданы ли все три аргумента. Если это так, что функция просто вычисляет и возвращает объём. в противном случае создаётся новая функция, которая устанавливает значения недостающих аргументов по своему усмотрению исходя из полученных ранее данных.

Например, нам нужно будет вычислять объёмы параллелепипедов, длина которых всегда равна десяти. Традиционным решением было бы передавать 10 первым аргументом при каждом вызове функции. Или же, пользуясь нашим решением, мы можем сначала создать частичную функцию, передав ей только одно значение, а в последующих вызовах передавать лишь два недостающих значения.

Карринг — это частный случай частичных функций, при котором функция, принимающая несколько аргументов, преобразуется в несколько функций, каждая из которых принимает один аргумент. То есть, вместо f (x,y,z) будет f (x)(y)(z) (хотя синтаксис PHP этого и не позволяет). Если вам интересны подробности реализации данного подхода, обратитесь к статье Timothy Boronczyk о карринге в PHP.

Преимущества и недостатки

Существует множество способов применения функционального программирования в PHP. Например, лямбда-функции широко применяются в качестве callback-функций. Так, например, в Slim Framework вы можете определить действие для роута следующим образом:

Не так давно Vance Lucas опубликовал обзор некоторых интересных вариантов использования лямбда-функций.

Безопасное программирование реализуется за счёт избегания состояний и изменяемых данных. Программируя функционально, вы должны создавать функции такими, чтобы каждая из них решала лишь одну задачу и не производила побочных эффектов. Такая парадигма, ставящая акцент на модульности и краткости функций, поможет сделать вашу программу проще для понимания.

Функциональное программирование также может помочь вам создавать код, который занят решением задач, не привнося накладных расходов на управление процессом вычислений (возьмите, хотя бы, рекурсию и сравните с необходимостью управлять счётчиками в циклах).

Однако не забывайте, что некоторые полезные штуки из функционального программирования вы не сможете реализовать в PHP по той простой причине. что этот язык изначально для этого не проектировался. Например, чистые функции являются отличными кандидатами для работы параллельных вычислениях, но сам PHP, к сожалению, для этого совершенно не годится.

Также имейте ввиду, что не всегда просто удаётся обходится одной рекурсией и ленивыми функциями, более того, они обычно требуют дополнительных ресурсов, что приводит к значительному снижению производительности в отдельных случаях. Иногда всё-таки гораздо эффективней разрабатывать программы в терминах изменяемости.

Возможно, наибольшим недостатком функционального программирования является довольно большой объём информации, который необходимо изучить мозгу, привыкшему к императивной парадигме программирования. Но несмотря ни на что, функциональное программирование крайне интересно, и изучая его, вы получите возможность увидеть решение некоторых задач в новом свете, что в свою очередь поможет вам продолжать расти как программисту. Функциональное программирование не есть панацея, но если грамотно и к месту им пользоваться, вы на порядок сможете улучшить читаемость и выразительность вашего кода.

Итоги

Функциональное программирование — это нечто большее, чем просто парадигма. Это определённый образ мышления при разработке программного обеспечения. Вы обязательно сможете найти моменты в ваших разработках, где функциональные приёмы, рассмотренные в этой статье, придутся к месту. Пробуйте, учитесь и получайте удовольствие!

Источник: SitePoint