Funkcje

Funkcje są podstawową jednostką wykonywania programu w dowolnym języku programowania. Podobnie jak w innych językach, funkcja F# ma nazwę, może mieć parametry i przyjmować argumenty i ma treść. Język F# obsługuje również konstrukcje programowania funkcjonalnego, takie jak traktowanie funkcji jako wartości, używanie nienazwanych funkcji w wyrażeniach, kompozycja funkcji do tworzenia nowych funkcji, funkcji curried i niejawnej definicji funkcji za pomocą częściowego stosowania argumentów funkcji.

Funkcje definiuje się przy użyciu słowa kluczowego let lub, jeśli funkcja jest rekursywna, kombinacja słowa kluczowego let rec .

Składnia

// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body

Uwagi

Nazwa-funkcji jest identyfikatorem reprezentującym funkcję. Lista parametrów składa się z kolejnych parametrów rozdzielonych spacjami. Dla każdego parametru można określić jawny typ, zgodnie z opisem w sekcji Parametry. Jeśli nie określisz określonego typu argumentu, kompilator próbuje wywnioskować typ z treści funkcji. Treść funkcji składa się z wyrażenia. Wyrażenie tworzące treść funkcji jest zazwyczaj wyrażeniem złożonym składającym się z wielu wyrażeń zakończonych w ostatnim wyrażeniu, które jest wartością zwracaną. Zwracany typ jest dwukropkiem, po którym następuje typ i jest opcjonalny. Jeśli jawnie nie określisz typu wartości zwracanej, kompilator określa typ zwracany z końcowego wyrażenia.

Prosta definicja funkcji przypomina następujące elementy:

let f x = x + 1

W poprzednim przykładzie nazwa funkcji to f, argument to x, który ma typ int, treść funkcji to x + 1, a zwracana wartość jest typu int.

Funkcje można oznaczyć jako inline. Aby uzyskać informacje na temat inlineprogramu , zobacz Funkcje wbudowane.

Zakres

Na dowolnym poziomie zakresu innego niż zakres modułu, nie jest to błąd ponownego użycia wartości lub nazwy funkcji. W przypadku ponownego użycia nazwy nazwa zadeklarowana później cieniuje nazwę zadeklarowaną wcześniej. Jednak w zakresie najwyższego poziomu w module nazwy muszą być unikatowe. Na przykład poniższy kod generuje błąd, gdy pojawia się on w zakresie modułu, ale nie wtedy, gdy pojawia się wewnątrz funkcji:

let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 () =
   let list1 = [1; 2; 3]
   let list1 = []
   list1

Jednak następujący kod jest akceptowalny na dowolnym poziomie zakresu:

let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
   let list1 = [1; 5; 10]
   x + List.sum list1

Parametry

Nazwy parametrów są wyświetlane po nazwie funkcji. Można określić typ parametru, jak pokazano w poniższym przykładzie:

let f (x : int) = x + 1

W przypadku określenia typu następuje nazwa parametru i jest oddzielona od nazwy dwukropkiem. Jeśli pominiesz typ parametru, typ parametru zostanie wywnioskowany przez kompilator. Na przykład w poniższej definicji funkcji argument x jest wnioskowany jako typ int , ponieważ 1 jest typu int.

let f x = x + 1

Jednak kompilator podejmie próbę tak ogólnego działania, jak to możliwe. Zwróć na przykład uwagę na następujący kod:

let f x = (x, x)

Funkcja tworzy krotkę na podstawie jednego argumentu dowolnego typu. Ponieważ typ nie jest określony, funkcja może być używana z dowolnym typem argumentu. Aby uzyskać więcej informacji, zobacz Automatyczne uogólnienie.

Funkcja fragmentów

Treść funkcji może zawierać definicje zmiennych lokalnych i funkcji. Takie zmienne i funkcje znajdują się w zakresie w treści bieżącej funkcji, ale nie poza nią. Należy użyć wcięcia, aby wskazać, że definicja znajduje się w treści funkcji, jak pokazano w poniższym przykładzie:

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

Aby uzyskać więcej informacji, zobacz Wytyczne dotyczące formatowania kodu i Składnia pełne.

Wartości zwrócone

Kompilator używa końcowego wyrażenia w treści funkcji, aby określić wartość zwracaną i typ. Kompilator może wywnioskować typ końcowego wyrażenia z poprzednich wyrażeń. W funkcji cylinderVolume, pokazanej w poprzedniej sekcji, typ pi jest określany z typu literału 3.14159 na floatwartość . Kompilator używa typu , pi aby określić typ wyrażenia length * pi * radius * radius na wartość float. W związku z tym ogólny zwracany typ funkcji to float.

Aby jawnie określić typ zwracany, napisz kod w następujący sposób:

let cylinderVolume radius length : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius

Jak pisano powyżej kod, kompilator stosuje zmiennoprzecinkowy do całej funkcji; jeśli chcesz również zastosować go do typów parametrów, użyj następującego kodu:

let cylinderVolume (radius : float) (length : float) : float

Wywołanie funkcji

Funkcje są wywoływane przez określenie nazwy funkcji, po której następuje spacja, a następnie wszelkie argumenty oddzielone spacjami. Aby na przykład wywołać cylindra funkcjiVolume i przypisać wynik do wartości vol, napisz następujący kod:

let vol = cylinderVolume 2.0 3.0

Częściowe stosowanie argumentów

Jeśli podasz mniej niż określona liczba argumentów, utworzysz nową funkcję, która oczekuje pozostałych argumentów. Ta metoda obsługi argumentów jest określana jako currying i jest cechą funkcjonalnych języków programowania, takich jak F#. Załóżmy na przykład, że pracujesz z dwoma rozmiarami rury: jeden ma promień 2,0 , a drugi ma promień 3,0. Można utworzyć funkcje, które określają objętość potoku w następujący sposób:

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining
// argument:

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

Następnie należy podać końcowy argument zgodnie z potrzebami dla różnych długości rury o dwóch różnych rozmiarach:

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

Funkcje rekursywne

Funkcje rekursywne to funkcje , które są wywoływane samodzielnie. Wymagają one określenia słowa kluczowego rec po słowie kluczowym let . Wywołaj funkcję rekursywną z poziomu treści funkcji tak samo jak wywołanie dowolnego wywołania funkcji. Poniższa funkcja rekursywna oblicza nnumer fibonacciego. Sekwencja liczb Fibonacciego jest znana od czasu starożytności i jest sekwencją, w której każda kolejna liczba jest sumą poprzednich dwóch liczb w sekwencji.

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

Niektóre funkcje rekursywne mogą przepełniać stos programu lub wykonywać nieefektywnie, jeśli nie piszesz ich z ostrożnością i świadomością specjalnych technik, takich jak użycie rekursji ogonowej, akumulatorów i kontynuacji.

Wartości funkcji

W języku F#wszystkie funkcje są traktowane jako wartości; w rzeczywistości są one nazywane wartościami funkcji. Ponieważ funkcje są wartościami, mogą być używane jako argumenty do innych funkcji lub w innych kontekstach, w których są używane wartości. Poniżej przedstawiono przykład funkcji, która przyjmuje wartość funkcji jako argument:

let apply1 (transform : int -> int ) y = transform y

Typ wartości funkcji należy określić przy użyciu tokenu -> . Po lewej stronie tego tokenu jest typ argumentu, a po prawej stronie jest zwracana wartość. W poprzednim przykładzie jest to funkcja, apply1 która przyjmuje funkcję jako argument, gdzie transform jest funkcjątransform, która przyjmuje liczbę całkowitą i zwraca inną liczbę całkowitą. Poniższy kod pokazuje, jak używać polecenia apply1:

let increment x = x + 1

let result1 = apply1 increment 100

Wartość result będzie wynosić 101 po uruchomieniu poprzedniego kodu.

Wiele argumentów jest oddzielonych kolejnymi -> tokenami, jak pokazano w poniższym przykładzie:

let apply2 ( f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

Wynik to 200.

Wyrażenia lambda

Wyrażenie lambda jest nienazwaną funkcją. W poprzednich przykładach zamiast definiować nazwane funkcje przyrostowe i mul, można użyć wyrażeń lambda w następujący sposób:

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y ) 10 20

Wyrażenia lambda są definiowane przy użyciu słowa kluczowego fun . Wyrażenie lambda przypomina definicję funkcji, z tą różnicą, że zamiast tokenu =-> token jest używany do oddzielenia listy argumentów od treści funkcji. Podobnie jak w definicji funkcji regularnej typy argumentów można wywnioskować lub jawnie określić, a zwracany typ wyrażenia lambda jest wnioskowany z typu ostatniego wyrażenia w treści. Aby uzyskać więcej informacji, zobacz Wyrażenia lambda: Słowo fun kluczowe.

Pipelines

Operator |> potoku jest szeroko używany podczas przetwarzania danych w języku F#. Ten operator umożliwia ustanowienie "potoków" funkcji w elastyczny sposób. Potokowanie umożliwia łączenie wywołań funkcji jako kolejnych operacji:

let result = 100 |> function1 |> function2

W poniższym przykładzie przedstawiono sposób tworzenia prostego potoku funkcjonalnego za pomocą tych operatorów:


/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
    values
    |> List.filter (fun x -> x % 2 <> 0)
    |> List.map (fun x -> x * x + 1)

let numbers = [ 1; 2; 3; 4; 5 ]

let result = squareAndAddOdd numbers

Wynikiem jest [2; 10; 26]. W poprzednim przykładzie użyto funkcji przetwarzania listy, pokazując, jak funkcje mogą być używane do przetwarzania danych podczas tworzenia potoków. Sam operator potoku jest zdefiniowany w bibliotece podstawowej języka F# w następujący sposób:

let (|>) x f = f x

Kompozycja funkcji

Funkcje w języku F# mogą składać się z innych funkcji. Kompozycja dwóch funkcji function1 i function2 jest inną funkcją, która reprezentuje aplikację funkcji function1 , a następnie aplikację funkcji 2:

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

Wynik to 202.

Operator >> kompozycji przyjmuje dwie funkcje i zwraca funkcję. Natomiast operator |> potoku przyjmuje wartość i funkcję i zwraca wartość. Poniższy przykład kodu przedstawia różnicę między operatorami potoku i kompozycji, pokazując różnice w podpisach funkcji i użyciu.

// Function composition and pipeline operators compared.

let addOne x = x + 1
let timesTwo x = 2 * x

// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo

// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo

// Result is 5
let result1 = Compose1 2

// Result is 6
let result2 = Compose2 2

// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

Przeładowywanie funkcji

Metody typu można przeciążyć, ale nie funkcje. Aby uzyskać więcej informacji, zobacz Metody.

Zobacz też