Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A rec
rendszer a let
kulcsszóval együtt használ egy rekurzív függvényt.
Syntax
// 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
...
Megjegyzések
A rekurzív függvények – magukat nevező függvények – kifejezetten az F# nyelvben vannak azonosítva a rec
kulcsszóval. A rec
kulcsszó elérhetővé teszi a kötés nevét a let
törzsében.
Az alábbi példa egy rekurzív függvényt mutat be, amely az n-edik Fibonacci-számot matematikai definícióval számítja ki.
let rec fib n =
match n with
| 0 | 1 -> n
| n -> fib (n-1) + fib (n-2)
Feljegyzés
A gyakorlatban az előző mintához hasonló kód nem ideális, mert szükségtelenül újrakomponálja a már kiszámított értékeket. Ennek az az oka, hogy nem a farok rekurzív, amelyet ebben a cikkben részletesebben ismertetünk.
A metódusok implicit módon rekurzívak a definiált típuson belül, ami azt jelenti, hogy nincs szükség a rec
kulcsszó hozzáadására. Példa:
type MyClass() =
member this.Fib(n) =
match n with
| 0 | 1 -> n
| n -> this.Fib(n-1) + this.Fib(n-2)
let
az osztályokon belüli kötések azonban nem implicit módon rekurzívak. Minden let
kötött függvényhez szükség van a kulcsszóra rec
.
Farok rekurziója
Egyes rekurzív függvények esetében újra kell dolgozni egy "tiszta" definíciót egy olyanra, amely a tail rekurzív. Ez megakadályozza a szükségtelen újraszámításokat. Az előző Fibonacci számgenerátor például a következőképpen írható át:
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
A Fibonacci-szám létrehozása nagyszerű példa egy olyan "naiv" algoritmusra, amely matematikailag tiszta, de a gyakorlatban nem hatékony. Bár ez egy bonyolultabb implementáció, több szempont is hatékonyabbá teszi az F#-ban, miközben továbbra is rekurzívan definiálva marad:
- Egy rekurzív belső függvény,
loop
amely egy idiomatikus F# minta. - Két akkumulátorparaméter, amelyek a halmozott értékeket adják át a rekurzív hívásoknak.
- Egy adott akkumulátor visszaadásának értékének
n
ellenőrzése.
Ha ez a példa iteratív módon lett megírva egy hurokkal, a kód két különböző értékhez hasonlóan fog kinézni, amelyek számokat halmoznak fel, amíg egy adott feltétel teljesül.
Ennek oka a tail-rekurzív, mert a rekurzív hívásnak nem kell értékeket mentenie a hívásveremen. A kiszámítandó köztes értékek a belső függvény bemeneteivel halmozódnak fel. Ez azt is lehetővé teszi, hogy az F#-fordító ugyanolyan gyorsan optimalizálja a kódot, mintha hurkot while
írt volna.
Gyakori, hogy olyan F#-kódot írunk, amely rekurzív módon feldolgoz valamit egy belső és külső függvénnyel, ahogy az előző példa is mutatja. A belső függvény a farok rekurzióját használja, míg a külső függvény jobb felületet biztosít a hívók számára.
Az F# 8.0-tól kezdődően az TailCall
attribútum használatával explicit módon megadhatja a tail-rekurzív függvény fordítóhoz való definiálásának szándékát. A fordító ezután figyelmezteti, ha a függvény nem tail rekurzív hívásokat indít. Az attribútum metódusokon és modulszintű függvényeken használható.
Használja például az első fib
definícióban:
[<TailCall>]
let rec fib n =
match n with
| 0 | 1 -> n
| n -> fib (n-1) + fib (n-2)
a két nem tail rekurzív hívásra vonatkozó fordítói figyelmeztetéseket váltana ki.
Kölcsönösen rekurzív függvények
Néha a függvények kölcsönösen rekurzívak, ami azt jelenti, hogy a hívások kört alkotnak, ahol az egyik függvény meghív egy másikat, amely viszont az elsőt hívja, tetszőleges számú hívás között. Ezeket a függvényeket egyetlen let
kötésben kell definiálnia, a and
kulcsszóval összekapcsolva őket.
Az alábbi példa két kölcsönösen rekurzív függvényt mutat be.
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)
Rekurzív értékek
Definiálhat -bound értéket is let
, hogy rekurzív legyen. Ez néha a naplózáshoz szükséges. Az F# 5 és a nameof
függvény segítségével a következőt teheti:
let rec nameDoubles = nameof nameDoubles + nameof nameDoubles