Найти в Дзене

Основы функциональщины: хвостовая рекурсия

В программировании в функциональном стиле, в том числе и на Scala, стоит придерживаться основных правил, таких, например, как во избежание использования циклов юзайте рекурсию. Вы можете, конечно, использовать и while, и for(хотя в скале for - немножко не тот for, что вы знаете, но мы сейчас не об этом), но по ряду причин лучше придерживаться хорошего тона. Что такое рекурсия, спросите вы? Тут всё просто это "функция, которая вызывает сама себя", прям Уробо́рос, ни больше, ни меньше. Пожалуй главное, чего нельзя забывать - ставить условие выхода из функции, иначе выйдет таки бесконечный цикл, и вы свалитесь с перегрузкой стека. Такие методы зачастую используются при обходе бинарных деревьев, например. Но если вы используете обычную рекурсию, будьте бдительны. Давайте рассмотрим два примера: def recursion(nb: Int): Int = {
if (nb == 0) 0
else nb + recursion(nb - 1)
}
@tailrec
def tailRecursion(nb: Int, acc: Int = 0): Int = {
if (nb == 0) acc
else tailRecursion(nb - 1, acc + nb)

В программировании в функциональном стиле, в том числе и на Scala, стоит придерживаться основных правил, таких, например, как во избежание использования циклов юзайте рекурсию. Вы можете, конечно, использовать и while, и for(хотя в скале for - немножко не тот for, что вы знаете, но мы сейчас не об этом), но по ряду причин лучше придерживаться хорошего тона.

Что такое рекурсия, спросите вы? Тут всё просто это "функция, которая вызывает сама себя", прям Уробо́рос, ни больше, ни меньше. Пожалуй главное, чего нельзя забывать - ставить условие выхода из функции, иначе выйдет таки бесконечный цикл, и вы свалитесь с перегрузкой стека. Такие методы зачастую используются при обходе бинарных деревьев, например. Но если вы используете обычную рекурсию, будьте бдительны. Давайте рассмотрим два примера:

def recursion(nb: Int): Int = {
if (nb == 0) 0
else nb +
recursion(nb - 1)
}

@tailrec
def tailRecursion(nb:
Int, acc: Int = 0): Int = {
if (nb == 0) acc
else
tailRecursion(nb - 1, acc + nb)
}

Это две функции, делающие, по сути, одно и то же: суммируют все числа от нуля до заданного числа, что же в них разного? В первом варианте дана обычная рекурсия, во втором же - хвостовая. В хвостовой рекурсии есть одно обязательное условие - последней строкой она должна вызывать себя, и ничего более(в первом варианте к ней добавляется число nb перед вызовом, в одной строке, что нарушает правило), а сумма записывается в аккумулятор, подготовленный в теле параметров.

Вы спросите, в чём разница-то? Хвостовая рекурсия перезатирает вызов в стеке , что не позволяет ему свалиться от перенаполнения. Если вы боитесь напутать с построением функции - добавляйте @tailrec перед началом функции, как показано в примере, данная аннотация говорит компилятору : это хвостовая рекурсия, и если Вы ошиблись, то Ваша программа не запустится.

Привыкание к написанию рекурсий требует времени, но оно того стоит!