fun-girlz


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: 
let rec infinite i = 1 + infinite (i + 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: 
let infinite' i =
    let rec inf_ i acc : int = inf_ (i+1) (acc+1)
    inf_ i 0

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: 
let rec fib n =
    if n <= 1 then 1
    else fib (n-1) + fib (n-2)

let fibt n =
    let rec fib_ n a b =
        if n = 0 then a
        else fib_ (n-1) (a + b) a
    fib_ n 1 0

Żeby przetestować optymalizację włączymy licznik czasu w FSI

1: 
#time "on";;

A następnie obliczymy wartość

1: 
2: 
fib 40;;
fibt 40;;

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: 
let rec map f l =
    match l with
    | h :: t -> f h :: map f t
    | []     -> []

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: 
let plus1 = List.map (fun x -> 1 + x)

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: 
let rec fold f v l =
    match l with
    | [] -> v
    | h :: t -> fold f (f v h) t

let sum = fold (+) 0

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: 
let mapf f l = List.rev (fold (fun acc e -> f e :: acc) [] l)

Zadania

  1. Zaimplementuj funkcję filter o sygnaturze ('a -> bool) -> 'a list -> 'a list
  2. Używając powyższej oraz ogonowej implementacji funkcji len napisz prostą funkcję count (można zaimplementować len przez fold)
  3. Napisz prawostronny foldr, który pozwoli napisać mapfr bez potrzeby List.rev (foldr nie będzie ogonowy)
  4. 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

val infinite : i:int -> int

Full name: Tailr.infinite
val i : int
val infinite' : i:int -> int

Full name: Tailr.infinite'
val inf_ : (int -> int -> int)
val acc : int
Multiple items
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<_>
val fib : n:int -> int

Full name: Tailr.fib
val n : int
val fibt : n:int -> int

Full name: Tailr.fibt
val fib_ : (int -> int -> int -> int)
val a : int
val b : int
val map : f:('a -> 'b) -> l:'a list -> 'b list

Full name: Tailr.map
val f : ('a -> 'b)
val l : 'a list
val h : 'a
val t : 'a list
val mapt : f:('a -> 'b) -> l:'a list -> 'b list

Full name: Tailr.mapt
val reverse : ('c list -> 'c list -> 'c list)
val l : 'c list
val r : 'c list
val h : 'c
val t : 'c list
val map_ : ('a list -> 'b list -> 'b list)
val acc : 'b list
val plus1 : (int list -> int list)

Full name: Tailr.plus1
Multiple items
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<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val x : int
val fold : f:('a -> 'b -> 'a) -> v:'a -> l:'b list -> 'a

Full name: Tailr.fold
val f : ('a -> 'b -> 'a)
val v : 'a
val l : 'b list
val h : 'b
val t : 'b list
val sum : (int list -> int)

Full name: Tailr.sum
val mapf : f:('a -> 'b) -> l:'a list -> 'b list

Full name: Tailr.mapf
val rev : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.rev
val e : 'a
F# Project