Можно ли реализовать хвостовую рекурсию с методами, возвращающими задачи?
В следующем примере не создается правильная функция хвостовой рекурсии:
[<TailCall>]
let rec isThisPossible () =
task {
// Do some stuff with tasks here ...
// Warning: FS3569
return! isThisPossible()
}
Когда я заменяю task
на async
, предупреждение компилятора исчезает, и я могу запустить свой пример без переполнения стека.
@Foxy Но async
, кажется, работает. Это особая реализация?
Простой ответ на ваш вопрос: нет, пока нет. Async
был реализован более 14 лет назад, и первоклассная поддержка хвостовой рекурсии является неотъемлемой основной функцией. Построитель task
был представлен в F# 6, а хвостовая рекурсия пока явно не поддерживается. На различных этапах ведутся языковые дискуссии, охватывающие а) перестройку async
для отработки механизмов, лежащих в основе task
б) заполнение пробелов в том, что task
не может делать хорошо, что async
может, например, хвостовая рекурсия, но и другие части
@RubenBartelink Спасибо за ответ. Я извлек данные, связанные с задачей, в функцию и вызвал ее с помощью Async.AwaitTask
в функции асинхронной хвостовой рекурсии.
Да, в принципе, это должно быть достойным обходным путем (плюс-минус Async.AwaitTask, делающий дурацкую упаковку github.com/fsharp/fslang-suggestions/issues/840)
Как обсуждалось в комментариях, здесь возможно решение с рекурсивной асинхронной функцией и отдельной функцией задачи:
let taskFunction param : Task =
task {
// Do your task stuff here
return! Task.CompletedTask
}
[<TailCall>]
let rec recursiveAsyncFunction param =
async {
do! taskFunction param |> Async.AwaitTask
return! recursiveAsyncFunction param
}
Синтаксис вычислительных выражений F# представляет собой синтаксический сахар, который после очистки вызывает методы соответствующего класса-строителя (
.Bind
,.Return
,.ReturnFrom
, ...). Таким образом, хвостовые вызовы невозможны, по крайней мере, я так не думаю. На самом деле это может зависеть от реализации вычислительного выражения.