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
F#
// Non-recursive function definition.let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.letrecfunction-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:
F#
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:
F#
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:
F#
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:
F#
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.
F#
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:
F#
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:
F#
let cylinderVolume radius length =
// Define a local value pi.let pi = 3.14159
length * pi * radius * radius
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:
F#
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:
F#
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:
F#
let vol = cylinderVolume 2.03.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:
F#
let smallPipeRadius = 2.0let 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:
F#
let length1 = 30.0let length2 = 40.0let 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.
F#
letrec fib n = if n < 2then1else 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:
F#
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:
F#
let increment x = x + 1let 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:
F#
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 1020
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:
F#
let result3 = apply1 (fun x -> x + 1) 100let result4 = apply2 (fun x y -> x * y ) 1020
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:
F#
let result = 100 |> function1 |> function2
W poniższym przykładzie przedstawiono sposób tworzenia prostego potoku funkcjonalnego za pomocą tych operatorów:
F#
/// 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:
F#
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:
F#
let function1 x = x + 1let function2 x = x * 2let 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.
F#
// Function composition and pipeline operators compared.let addOne x = x + 1let timesTwo x = 2 * x
// Composition operator// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3let Compose2 = addOne >> timesTwo
// Backward composition operator// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3let Compose1 = addOne << timesTwo
// Result is 5let result1 = Compose1 2// Result is 6let result2 = Compose2 2// Pipelining// Pipeline operator// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'Ulet Pipeline2 x = addOne x |> timesTwo
// Backward pipeline operator// ( <| ) : ('T -> 'U) -> 'T -> 'Ulet Pipeline1 x = addOne <| timesTwo x
// Result is 5let result3 = Pipeline1 2// Result is 6let result4 = Pipeline2 2
Przeładowywanie funkcji
Metody typu można przeciążyć, ale nie funkcje. Aby uzyskać więcej informacji, zobacz Metody.
Dołącz do serii meetup, aby tworzyć skalowalne rozwiązania sztucznej inteligencji oparte na rzeczywistych przypadkach użycia z innymi deweloperami i ekspertami.