Какой стандартный способ оптимизировать взаимную рекурсию в F # / Scala?

StackOverflow https://stackoverflow.com/questions/2798831

Вопрос

Эти языки не поддерживают взаимно рекурсивные функции оптимизации «естественным», поэтому я думаю, что это должен быть батут или .. Хех .. Переписать как цикл) Я что-то пропускаю?

Обновление: кажется, что я лежал о 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 реализации на большинстве платформ почитают ее.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top