Пока мы рассмотрели только одну возможность объектов первого рода применительно к функциям — присвоение переменной. Самое интересное начинается, когда мы передаем одни функции в другие функции.
За примерами далеко ходить не придется. Вспомним сортировку. В PHP есть функция sort
, которая принимает на вход массив и сортирует его. Казалось бы, все отлично. Но давайте вообразим себе ситуацию, что на вход в программу приходит список пользователей, который нужно отсортировать по возрасту и вывести на экран:
<?php
$users = [
['name' => 'Igor', 'age' => 19],
['name' => 'Danil', 'age' => 1],
['name' => 'Vovan', 'age' => 4],
['name' => 'Matvey', 'age' => 16],
];
При таких условиях функция sort
становится абсолютно бесполезной, потому что она может сортировать только списки примитивных типов данных. Но выше мы описали всего одну из тысяч возможных ситуаций. Мы можем захотеть сортировать по любому параметру или набору параметров, причем в любом порядке. Сортировки нужны часто, и многие из них довольно сложны. Худшее, что можно начать делать — реализовывать функцию sort
под каждую ситуацию.
Так что же делать? В документации PHP можно обнаружить функцию usort. Она сортирует массив по значениям, используя пользовательскую функцию для сравнения элементов:
bool usort ( array &$array , callable $value_compare_func )
Эта функция сортирует элементы массива, используя для сравнения значений колбэк-функцию, предоставленную пользователем. Используйте эту функцию, если вам нужно отсортировать массив по какому-нибудь необычному признаку. Слово callback означает, что мы хотим передать функцию без вызова. Вызывать будет функция usort
.
Общая идея состоит в том, что нам не нужно реализовывать алгоритм сортировки каждый раз для каждой ситуации, ведь он не меняется. Все, что меняется — элементы, которые сравниваются между собой в процессе сортировки. Функция usort
делегирует взаимодействие с этими элементами вызывающему коду:
<?php
$cmp = function ($a, $b) {
if ($a['age'] === $b['age']) {
return 0;
}
return $a['age'] > $b['age'] ? 1 : -1;
};
usort($users, $cmp);
print_r($users);
// => [
// ['name' => 'Danil', 'age' => 1],
// ['name' => 'Vovan', 'age' => 4],
// ['name' => 'Matvey', 'age' => 16],
// ['name' => 'Igor', 'age' => 19],
// ];
https://replit.com/@hexlet/php-functions-high-order-functions-usort
Функция usort
выполняет всю работу по непосредственному перемещению элементов в массиве, но от нас зависит, какой элемент больше или меньше. Достигается подобная схема за счет той самой пользовательской функции, передающейся вторым параметром. Эта функция принимает на вход два параметра — usort
отдает в нее два элемента, которые она сравнивает в этот момент. В нашем случае элементы — это пользователи.
Наша задача — внутри этой функции посчитать, что больше или меньше, и сделать следующее:
- Если элементы равны, нужно вернуть
0
- Если первый элемент больше второго, нужно считать ошибку в сортировке и вернуть
1
- Если первый элемент меньше второго, нужно вернуть
-1
, аusort
произведет их сортировку
Из кода выше видно, что внутри функции сравнение переданных пользователей идет по свойству age
. Нетрудно догадаться, что эта функция вызывается внутри usort
множество раз, то есть на каждое сравнение. Как только она начнет возвращать -1
для каждой пары элементов, сортировка завершена.
Код выше можно упростить, воспользовавшись оператором <=>
(spaceship). Он возвращает 1
, -1
или 0
в зависимости от соотношения операндов и работает так, как того ожидает сортировка:
<?php
3 <=> 5; // -1 (выражение слева от оператора "<=>" меньше выражения справа)
5 <=> 5; // 0 (выражения слева и справа равны)
5 <=> 3; // 1 (выражение слева больше выражения справа)
Тогда код сокращается до такого:
<?php
$cmp = fn($a, $b) => $a['age'] <=> $b['age'];
usort($users, $cmp);
Функция usort
относится к функциям высшего порядка. Такие функции либо принимают другие функции, либо возвращают их, либо делают все сразу. Как правило, они реализуют сортировку или другой обобщенный алгоритм, а ключевую часть логики делегируют нам через колбэк-функцию. Главный плюс от применения таких функций — серьезное повышение коэффициента повторного использования кода.
В примере выше не обязательно создавать переменную для функции. Говоря откровенно, их вообще редко записывают в переменные. Типичное использование выглядит как прямая передача функции в функцию:
<?php
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
Потратьте немного времени и разберитесь, где заканчивается одна функция и начинается другая. Подобный код мы начнем использовать уже со следующего урока.
Осталось рассмотреть, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет:
<?php
function say(callable $fn)
{
echo $fn();
}
say(fn() => 'hi!'); // => hi!
Функция say
делает вызов функции, находящейся внутри переменной $fn
. В нашем примере функция возвращает строку, которая тут же выводится на экран.
Функции высшего порядка настолько удобны в большинстве языков, что практически целиком могут заменить использование тех же циклов. В следующих уроках мы рассмотрим три самые главные функции высшего порядка, которыми можно решать практически любые задачи.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.