Пока мы рассмотрели только одну возможность объектов первого рода применительно к функциям — присвоение переменной. Самое интересное начинается, когда мы передаём одни функции в другие функции.
За примерами далеко ходить не придётся. Вспомним сортировку. В 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-функцию, предоставленную пользователем. Используйте эту функцию, если вам нужно отсортировать массив по какому-нибудь необычному признаку. Слово 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
относится к так называемым функциям высшего порядка (high order functions). Функции высшего порядка — это функции, которые либо принимают, либо возвращают другие функции, либо делают все сразу. Такие функции, как правило, реализуют некий обобщённый алгоритм (например, сортировку), а ключевую часть логики делегируют вам через callback-функцию. Главный плюс от применения таких функций — серьёзное повышение коэффициента повторного использования кода.
В примере выше не обязательно создавать переменную для функции. Говоря откровенно, их вообще редко записывают в переменные. Типичное использование выглядит как прямая передача функции в функцию:
<?php
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
Потратьте немного времени, чтобы понять, где заканчивается одна функция и начинается другая. Подобный код мы начнём использовать уже со следующего урока.
Осталось рассмотреть то, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет.
<?php
function say(callable $fn)
{
echo $fn();
}
say(fn() => 'hi!'); // => hi!
Функция say
делает вызов функции, находящейся внутри переменной $fn
. В нашем примере функция возвращает строку, которая тут же выводится на экран.
Функции высшего порядка настолько удобны в большинстве языков, что практически целиком могут заменить использование тех же циклов. В следующих уроках мы рассмотрим три самые главные функции высшего порядка, которыми можно решать практически любые задачи.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт