Какой стандартный способ оптимизировать взаимную рекурсию в F # / Scala?
-
04-10-2019 - |
Вопрос
Эти языки не поддерживают взаимно рекурсивные функции оптимизации «естественным», поэтому я думаю, что это должен быть батут или .. Хех .. Переписать как цикл) Я что-то пропускаю?
Обновление: кажется, что я лежал о Fsharp, но я просто не видел пример взаимных хвостовых вызовов во время густоты
Решение
Прежде всего, F # поддерживает взаимно рекурсивные функции, поскольку он может извлечь выгоду из tailcall
Инструкция, доступная в .NET IL (MSDN). Тем не менее, это немного сложно и может не работать над некоторыми альтернативными реализациями .NET (например, компактные рамки), поэтому вам иногда может потребоваться дело с этим вручную.
В общем, я, что есть пара способов справиться с этим:
Батут - Выбросьте исключение, когда глубина рекурсии слишком высока и реализует цикл верхнего уровня, которая обрабатывает исключение (исключение будет иметь информацию для возобновления вызова). Вместо исключения вы также можете просто вернуть значение, указав, что функция должна быть вызвана снова.
Расслабиться с использованием таймера - Когда глубина рекурсии слишком высокая, вы создаете таймер и придайте ему обратный вызов, который будет называться таймером через некоторое время (таймер продолжит рекурсию, но использованный стек будет сброшен).
То же самое можно было сделать с помощью глобального стека, который хранит работу, которая должна быть сделана. Вместо планирования таймера вы добавили бы функцию в стек. На верхнем уровне программа выберет функции из стека и запустить их.
Чтобы дать конкретный пример первой техники, в F # вы можете написать это:
type Result<´T> =
| Done of ´T
| Call of (unit -> ´T)
let rec factorial acc n =
if n = 0 then Done acc
else Call(fun () -> factorial (acc * n) (n + 1))
Это может быть использовано для взаимно рекурсивных функций. Императивная петля просто позвонит f
Функция хранится в Call(f)
пока он не производит Done
с окончательным результатом. Я думаю, что это, наверное, самый чистый способ реализации этого.
Я уверен, что есть другие сложные методы для решения этой проблемы, но это два, о которых я знаю (и что я использовал).
Другие советы
На Scala 2.8, scala.util.control.TailCalls
:
import scala.util.control.TailCalls._
def isEven(xs: List[Int]): TailRec[Boolean] = if (xs.isEmpty)
done(true)
else
tailcall(isOdd(xs.tail))
def isOdd(xs: List[Int]): TailRec[Boolean] = if (xs.isEmpty)
done(false)
else
tailcall(isEven(xs.tail))
isEven((1 to 100000).toList).result
Просто чтобы код удостоверился, когда вы будете Bing for F # взаимной рекурсии:
let rec isOdd x =
if x = 1 then true else isEven (x-1)
and isEven x =
if x = 0 then true else isOdd (x-1)
printfn "%A" (isEven 10000000)
Это будет StackoverFlow, если вы компилируете без вызовов Hast (по умолчанию в режиме «Отладка», который сохраняет стеки для облегчения отладки), но запустите просто тонку при компиляции с вызовами хвоста (по умолчанию в режиме «Отпуска»). Компилятор делает вызова хвоста по умолчанию (см. - Отправляет вариант), а также .NET реализации на большинстве платформ почитают ее.