В этом тьюториале мы рассмотрим с вами, что такое лямбда-функции и замыкания, а также примеры их использования.
Чтобы выполнить задания, вам потребуются следующие программы:
Программное обеспечение или ресурс | Требуемая версия |
Denwer (PHP5.3) | 3 + |
Notepad, или любой текстовый редактор | — |
Mozilla Firefox | 3.6 + |
Примечания:
- Мы предполагаем, что у вас есть базовые знания PHP.
Что это за функции такие: лямбда и замыкание?
В PHP 5.3 есть множество интересных возможностей, которые приближают его синтаксис к таким языкам программирования как: JavaScript, Python, Ruby и т.д. И, что более важно, эти возможности полезны и стали популярными.
Лямбда-функции и замыкания — это программные объекты, не обладающие именем, они реализуются там, где потребовалась их помощь. Обратите внимание, лямбда-функции не захватывают контекст, а замыкания предназначены для его захвата. Сразу скажем, обе эти функций в PHP по типу соответствуют классу Closure, и указанные выше отличия не принципиальны для практического использования в PHP. В практике вы заметите только то, что от класса Closure ни наследоваться нельзя, ни экземпляры создавать. Его ввели только для внутреннего использования — для типизации.
Лямбда-функции
Цель:
- Научится использовать лямбда функции в качестве параметров других функций
Лямбда-функции, оказывается, очень полезны для “здоровья” некоторых функций, особенно тех, которые требуют в качестве параметра функцию.
Давайте рассмотрим функцию usort(), сортирующую любые массивы. Для своей работы она требует два параметра. Первый — массив, который надо отсортировать. Второй — функция, с двумя аргументами. В этой функции сравниваются два значения, которые ей передали. Она должна возвращать 0 если они равны, -1 если первое меньше и 1 если второе меньше.
Представьте, что нам всего в одном месте программы нужно отсортировать массив объектов по некоторому полю, нам не нужно объявлять отдельную функцию, мы воспользуемся лямбда-функцией. В Листинге №1 показано как использовать лямбда-функцию для сортировки массива.
Листинг №1 (строки лямбда-функции подсвечены):
<?php class naturalNumber { private $number; public function setNumber($number) { $this->number = $number; } public function getNumber() { return $this->number; } } $aNumber = array(); for($i = 0; $i < 10; $i++) { $oNumber = new naturalNumber(); $oNumber->setNumber(rand(1, 10)); $aNumber[] = $oNumber; } echo "До сортировки:<pre>"; print_r($aNumber); echo "</pre>"; usort($aNumber, function($oFirst, $oSecond) { if($oFirst->getNumber() < $oSecond->getNumber()) { return -1; } $result = ( $oFirst->getNumber() == $oSecond->getNumber() ) ? 0 : 1; return $result; } ); echo "После сортировки:<pre>"; print_r($aNumber); echo "</pre>"; ?>
В этом примере происходит заполнение массива $aNumber объектами, которые создаются на основе класса naturalNumber. В каждом объекте хранится случайное число, доступ к которому осуществляется через функции getNumber() и setNumber(). Затем происходит сортировка массива с использованием лямбда-функции. Для usort() необходимо, чтобы вторым параметром была указана функция с двумя аргументами, каждый из которых должен соответствовать по типу элементам сортируемого массива. После заполнения массива и в конце программы происходит вывод массива.
Замыкания
Цель:
- Использовать контекст функции
Замыкания тоже «вкусны» по-своему, оттенки их «вкуса» можно заметить не всегда. Их применение в коде похоже по стилю на JavaScript. Замыкания, кроме того что они используют переменные контекста, в котором они определены, обладают ещё одним замечательным свойством — их время жизни может быть больше, чем у той функции, в которой их определили.
Для примера захвата значений переменных, возьмём суммирование массива и возвращение результата в контекст программы. Если точнее, захватываться будет ссылка на переменную, в которой будет происходить суммирование. Для полного счастья, суммировать будем рекурсивно по массиву :), побываем во всех его уголках. Пример программы показан в Листинге №2:
Листинг №2 (строки замыкания подсвечены):
<?php $aText = array( array( 'JavaScript', 'это язык программирования', array( 'использующийся в веб-приложениях', array( 'на стороне клиента.' ) ), ), ); $text = ''; array_walk_recursive( $aText, function($item, $key) use(&$text) { $text .= ' ' . $item; } ); echo "$text<br />"; ?>
В этом примере происходит создание строки из многомерного массива, путём склеивания всех его элементов через пробел. Стандартно для склеивания используется функция implode(), но здесь не тот случай — она не склеивает многомерные массивы. Может для кого-то этот пример будет кстати.
Существует особенность, которую нужно учитывать, когда вы используете замыкание в методе (метод — функция, член класса). Оказывается оно (замыкание) не захватывает внутреннюю переменную $this (отвечающую за текущий экземпляр). Для того, чтобы её всё-таки передать в замыкание, нужно использовать локальную переменную, например вот так $self = $this, а затем уже передавать в замыкание use($self).
Способов применения этим функциям можно найти массу. К примеру, можно создавать проекты в стиле JavaScript, можно продлевать жизнь параметрам и переменным и ещё многое. Будем надеяться, что в PHP 6 этот механизм станет более похож на то, что уже есть в других языках.
Есть вопросы? Добро пожаловать в коментарии.
Хочется верить в PHP6, а то пока со статическими методами классов проблема. Разработчики молчат, хотя баг вывешен им…
Посмотрел ваш сайт, красиво ❗
А вот на счёт обращения через self внутри замыкания, я думаю это не баг. Просто эта фича не заложена пока в PHP. Вот и в документации пишут — если чего даёте на съедение замыканию, то только переменные и, пожалуйста, через use().
Задача, которая натолкнула на использование лямбда функций, была банальной — плагин к wordpress. class использовал с целью оформления namespace, но использовал статические методы класса. Ну и статические методы класса и пришлось регистрировать как «фильтры» и «actions» в терминологии wordpress. Тут и напрашивались лямбда функции. Но они (лямбда фукнции, определённые в коде статического метода класса) не относятся к методами класса (то есть видят только public члены класса и не могут неявно использовать self). Может есть уже и иное решения (ну, кроме того, что было до 5.3, «классику» знаю, но она избыточна и не особо читаема в ряде случаев)?
Похоже, что других вариантов пока нет. Придётся умножать сущности, т.е., вводить переменную, передавать ей значение и передавать её через use в замыкание.