Курс языка программирования Raku / Функциональное, конкурентное, реактивное и веб-программирование / Функциональное программирование / Рекурсия
Рекурсия с multi-подпрограммами
До сих пор базовый случай был строкой внутри подпрограммы —
тернарным оператором или ранним return, проверяющим
аргумент. Raku предлагает более выразительный способ записать то же
самое. Поскольку у подпрограммы может быть несколько кандидатов
multi, базовому случаю и рекурсивному шагу можно отдать
отдельные подпрограммы и позволить множественной
диспетчеризации выбирать нужную при каждом вызове.
Вспомните факториал. С multi два его случая становятся
двумя подпрограммами:
multi fact(0) { 1 }
multi fact($n) { $n * fact($n - 1) }
say fact(5); # 120Первый кандидат срабатывает, только когда аргумент в точности равен
0, — этот литерал в сигнатуре и есть базовый
случай. Все остальные вызовы уходят ко второму кандидату, который
умножает и рекурсивно вызывает себя. Когда fact($n - 1)
наконец дойдёт до 0, диспетчеризация переключится на
первого кандидата и цепочка вызовов развернётся обратно. Базовый случай
больше не ветка, спрятанная в теле; это подпрограмма, существующая ради
единственного значения.
Почему 0, а не 1? Потому что каждый шаг
вычитает единицу, и любое начальное число рано или поздно попадает точно
на 0, а 0! по определению равен
1, — значит, именно на 0 спуск по-настоящему
заканчивается. Кандидат с литералом срабатывает для одного
точного значения, поэтому база вида multi fact(1)
вычислила бы fact(1) верно, но fact(0)
провалился бы к multi fact($n) и уходил бы в рекурсию за
ноль бесконечно. Остановка на 0 сохраняет подпрограмму
правильной для любого неотрицательного целого числа, включая
fact(0).
Особенно хорошо это читается, когда базовых случаев больше одного. Числам Фибоначчи нужны два:
multi fib(0) { 0 }
multi fib(1) { 1 }
multi fib($n) { fib($n - 1) + fib($n - 2) }
say fib(10); # 55Каждый базовый случай — собственный однострочный кандидат, а рекурсивный кандидат обрабатывает всё остальное; никаких вложенных условий.
Литерал вроде 0 срабатывает только для этого точного
значения. Когда базовый случай покрывает диапазон —
«$n равно 1 или меньше», — используйте вместо
литерала ограничение where:
multi fact($n where * <= 1) { 1 }
multi fact($n) { $n * fact($n - 1) }
say fact(6); # 720Кандидат с ограничением более специфичен, поэтому Raku пробует его
первым; кандидат с простым $n ловит всё остальное.
Прежняя дисциплина остаётся в силе: каждый рекурсивный путь обязан
дойти до кандидата с базовым случаем. Факториал с литералом
0, например, безопасен только для неотрицательных целых
чисел — fact(-1) перешагнул бы через 0 и
уходил бы в рекурсию бесконечно, потому что ни один кандидат так и не
подошёл бы. Разнесение случаев по multi-подпрограммам не
отменяет необходимости в базовом случае; оно лишь даёт этому базовому
случаю имя и собственное место.
Практика
Пройдите 1 тест по содержанию этой темы.
Навигация по курсу
← Тест — Базовый случай | Тест — Рекурсия с multi-подпрограммами →
💪 Или перейдите сразу к упражнениям этого
раздела.