Pokaż mi swój ogon
Rekurencja to wielokrotne wołanie funkcji. Za każdym razem kiedy wołamy jakąś funkcję to odkładamy na stos operacyjny informację, gdzie powinniśmy wrócić po wykonaniu funkcji. Stos ma pewną wielkość i jeśli odłożymy na niego za dużo danych to znajdziemy się w bardzo złej sytuacji. Na platformie .NET dostaniemy wyjątek StackOverflowException
, którego nie da się obsłużyć i wykonywanie naszego programu zostanie przerwane. Przykładem może być taka oto nieskończona pętla:
1:
|
|
Jednak nie musi to być coś nieskończonego, wystarczy po prostu że ilość danych będzie dostatecznie duża. Co można zrobić?
Otóż nasz kompilator potrafi optymalizować wywołania rekurencyjne i produkować pętlę bez użycia stosu w kodzie wynikowym, ale tylko jeśli funkcja jest rekurencyjna ogonowo.
Ten ogon bierze się stąd, że ostatnim wyrażeniem nierozgałęziającym jest wywołanie tej samej funkcji, ale z odpowiednio innymi parametrami.
Większość rekurencji ogonowej opiera się o wzorzec akumulatora. Powyższą nieskończoną pętlę zapiszemy w poniższy sposób i o ile będzie się ona pętlić w nieskończoność, to nie poskutkuje śmiercią procesu.
1: 2: 3: |
|
Będziemy ten wzorzec wykorzystywać do optymalizacji naszego kodu. Zaczniemy od porównania liczenia n-tej liczby Fibonacciego.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Żeby przetestować optymalizację włączymy licznik czasu w FSI
1:
|
|
A następnie obliczymy wartość
1: 2: |
|
I porównamy czasy ich wykonania.
Funkcje wyższych rzędów
Użyjemy zdobytej przez nas dotychczas wiedzy, aby napisać funkcje, które ułatwiają życie podczas pracy z listami.
map
Przypomnijmy sobie funkcje plus1
i succL
. Miały one wspólną funkcjonalność. Brały listę, robiły coś dla każdego jej elementu, niezależnie od pozostałych elementów, i zwracały zmodyfikowaną listę. Jest to funkcjonalność bibliotecznej funkcji map
, którą właśnie zaimplementujemy.
map
ma sygnaturę ('a -> 'b) -> 'a list -> 'b list
Powiemy, że map
jest funkcją "wyższego rzędu" ponieważ jednym z jej parametrów jest funkcja.
1: 2: 3: 4: |
|
Teraz waszym zadaniem będzie ogonowa implementacja naszej funkcji map. Wzorcowe rozwiązanie poniżej.
Tak jak wcześniej, możemy przetestować działanie naszej implementacji map
i mapt
na funkcji fib
i fibt
oraz liście [1..40]
.
Często będziemy stosować map
z funkcjami anonimowymi:
1:
|
|
Powyżej wykorzystujemy częściową aplikację, żeby nie musieć podawać ostatniego parametru do funkcji map
(tym razem bibliotecznej) oraz anonimową funkcję dodającą jeden.
fold
Kolejną przydatną funkcją jest fold
który służy do zwijania listy (robienie z niej kuli do bałwanka) łącząc kolejne elementy pewną funkcją.
fold
ma sygnaturę ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
Przy jej pomocy zaimplementujemy w prosty sposób funkcję sumującą elementy listy.
1: 2: 3: 4: 5: 6: |
|
Jest to tzw. lewostronny fold
, czyli zaczynając od lewej strony bierzemy wartość początkową v
i rolujemy ją po liście aplikując funkcję f
. Jak widać fold
jest domyślnie ogonowy.
Możemy przy jego pomocy zaimplementować także nasz map
.
1:
|
|
Zadania
- Zaimplementuj funkcję
filter
o sygnaturze('a -> bool) -> 'a list -> 'a list
- Używając powyższej oraz ogonowej implementacji funkcji
len
napisz prostą funkcjęcount
(można zaimplementowaćlen
przezfold
) - Napisz prawostronny
foldr
, który pozwoli napisaćmapfr
bez potrzebyList.rev
(foldr
nie będzie ogonowy) - Napisz ogoną implementację silni (z akumulatorem) oraz wersję korzystającą tylko z funkcji wyższego rzędu
Pytania? Jeśli wszystko jasne, to przechodzimy do następnego modułu
Full name: Tailr.infinite
Full name: Tailr.infinite'
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Full name: Tailr.fib
Full name: Tailr.fibt
Full name: Tailr.map
Full name: Tailr.mapt
Full name: Tailr.plus1
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.map
Full name: Tailr.fold
Full name: Tailr.sum
Full name: Tailr.mapf
Full name: Microsoft.FSharp.Collections.List.rev