Funções recursivas: a palavra-chave rec
A palavra-chave rec
é usada junto com a palavra-chave let
para definir uma função recursiva.
Sintaxe
// Recursive function:
let rec function-name parameter-list =
function-body
// Mutually recursive functions:
let rec function1-name parameter-list =
function1-body
and function2-name parameter-list =
function2-body
...
Comentários
As funções recursivas – funções que se autodenominam – são identificadas explicitamente na linguagem F# com a palavra-chave rec
. A palavra-chave rec
disponibiliza o nome da associação let
em seu corpo.
O exemplo a seguir mostra uma função recursiva que calcula o número Fibonaccinth usando a definição matemática.
let rec fib n =
match n with
| 0 | 1 -> n
| n -> fib (n-1) + fib (n-2)
Observação
Na prática, código como o exemplo anterior não é ideal porque recompõe desnecessariamente valores que já foram calculados. Isso ocorre porque ele não é uma recursiva de cauda, o que é explicado mais adiante neste artigo.
Os métodos são implicitamente recursivos dentro do tipo em que são definidos, o que significa que não há necessidade de adicionar a palavra-chave rec
. Por exemplo:
type MyClass() =
member this.Fib(n) =
match n with
| 0 | 1 -> n
| n -> this.Fib(n-1) + this.Fib(n-2)
No entanto, as associações let
as associações dentro das classes não são implicitamente recursivas. Todas as funções associadas let
exigem a palavra-chave rec
.
Recursão de cauda
Para algumas funções recursivas, é necessário refatorar uma definição mais "pura" para uma que seja recursiva de cauda. Isso evita recálculos desnecessários. Por exemplo, o gerador de números Fibonacci anterior pode ser reescrito da seguinte maneira:
let fib n =
let rec loop acc1 acc2 n =
match n with
| 0 -> acc1
| 1 -> acc2
| _ ->
loop acc2 (acc1 + acc2) (n - 1)
loop 0 1 n
Gerar um número Fibonacci é um grande exemplo de um algoritmo "ingênuo" matematicamente puro, mas ineficiente na prática. Embora essa seja uma implementação mais complicada, vários aspectos a tornam eficiente em F#, enquanto ainda permanecem recursivamente definidos:
- Uma função interna recursiva chamada
loop
, que é um padrão F# idiomático. - Dois parâmetros de acumulador, que passam valores acumulados para chamadas recursivas.
- Uma verificação sobre o valor de
n
para retornar um acumulador específico.
Se este exemplo fosse escrito iterativamente com um loop, o código seria semelhante com dois valores diferentes acumulando números até que uma condição específica fosse atendida.
O motivo pelo qual isso é recursivo é porque a chamada recursiva não precisa salvar nenhum valor na pilha de chamadas. Todos os valores intermediários que estão sendo calculados são acumulados por meio de entradas para a função interna. Isso também permite que o compilador F# otimize o código para ser tão rápido quanto se você tivesse escrito algo como um loop while
.
É comum escrever código F# que processa recursivamente algo com uma função interna e externa, como mostra o exemplo anterior. A função interna usa a recursão final, enquanto a função externa tem uma interface melhor para os chamadores.
A partir do F# 8.0, você pode usar o atributo TailCall
para declarar explicitamente sua intenção de definir uma função recursiva de cauda para o compilador. O compilador avisará você se a sua função fizer chamadas recursivas sem caudas. Você pode usar o atributo em métodos e funções no nível do módulo.
Por exemplo, usá-lo na primeira definição de fib
:
[<TailCall>]
let rec fib n =
match n with
| 0 | 1 -> n
| n -> fib (n-1) + fib (n-2)
dispararia avisos do compilador sobre as duas chamadas recursivas sem caudas.
Funções mutuamente recursivas
Às vezes, as funções são mutuamente recursivas, o que significa que as chamadas formam um círculo, no qual uma função chama outra que, por sua vez, chama a primeira, com qualquer número de chamadas entre elas. Você deve definir essas funções em uma associação let
, usando a palavra-chave and
para vinculá-las.
O exemplo a seguir mostra duas funções mutuamente recursivas.
let rec Even x = if x = 0 then true else Odd(x - 1)
and Odd x = if x = 0 then false else Even(x - 1)
Valores recursivos
Você também pode definir um valor associado a let
a ser recursivo. Às vezes, isso é feito para registro em log. Com o F# 5 e a função nameof
, você pode fazer isso:
let rec nameDoubles = nameof nameDoubles + nameof nameDoubles
Confira também
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de