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 inline
programu , 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 float
wartość . 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.