Szukaj…


Funkcje więcej niż jednego parametru

W języku F # wszystkie funkcje przyjmują dokładnie jeden parametr . Wydaje się to dziwną instrukcją, ponieważ w deklaracji funkcji można w prosty sposób zadeklarować więcej niż jeden parametr:

let add x y = x + y

Ale jeśli wpiszesz tę deklarację funkcji w interaktywnym interpretatorze F #, zobaczysz, że jej podpis typu to:

val add : x:int -> y:int -> int

Bez nazw parametrów podpis ten to int -> int -> int . Operator -> jest asocjacyjnie prawy, co oznacza, że ten podpis jest równoważny int -> (int -> int) . Innymi słowy, add jest funkcją, która przyjmuje jeden parametr int i zwraca funkcję, która przyjmuje jeden int i zwraca int . Spróbuj:

let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5

Możesz jednak również wywołać funkcję typu add w bardziej „konwencjonalny” sposób, bezpośrednio przekazując jej dwa parametry, i zadziała tak, jak można się spodziewać:

let three = add 1 2
// val three : int = 3

Dotyczy to funkcji z dowolną liczbą parametrów:

let addFiveNumbers a b c d e = a + b + c + d + e
// val addFiveNumbers : a:int -> b:int -> c:int -> d:int -> e:int -> int
let addFourNumbers = addFiveNumbers 0
// val addFourNumbers : (int -> int -> int -> int -> int)
let addThreeNumbers = addFourNumbers 0
// val addThreeNumbers : (int -> int -> int -> int)
let addTwoNumbers = addThreeNumbers 0
// val addTwoNumbers : (int -> int -> int)
let six = addThreeNumbers 1 2 3  // This will calculate 0 + 0 + 1 + 2 + 3
// val six : int = 6

Ta metoda myślenia o funkcjach wieloparametrowych jako o funkcjach, które pobierają jeden parametr i zwracają nowe funkcje (które z kolei mogą przyjmować jeden parametr i zwracać nowe funkcje, dopóki nie dojdziesz do funkcji końcowej, która przyjmuje parametr końcowy i ostatecznie zwraca wynik) nazywa currying na cześć matematyka Haskell Curry, który jest znany z opracowania koncepcji. (Został wymyślony przez kogoś innego, ale Curry zasłużył sobie na większość zasług).

Ta koncepcja jest używana w całym F # i będziesz chciał się z nią zapoznać.

Podstawy funkcji

Większość funkcji w języku F # jest tworzonych ze składnią let :

let timesTwo x = x * 2

Definiuje funkcję o nazwie timesTwo która przyjmuje pojedynczy parametr x . Jeśli uruchomisz interaktywną sesję F # ( fsharpi w systemie OS X i Linux, fsi.exe w systemie Windows) i wkleisz tę funkcję (i dodasz ;; mówi fsharpi aby ocenił właśnie wpisany kod), zobaczysz, że to odpowiada:

val timesTwo : x:int -> int

Oznacza to, że timesTwo jest funkcją, która pobiera pojedynczy parametr x typu int i zwraca int . Podpisy funkcji są często zapisywane bez nazw parametrów, więc często zobaczysz ten typ funkcji zapisany jako int -> int .

Ale poczekaj! Skąd F # wiedział, że x jest parametrem liczby całkowitej, skoro nigdy nie podałeś jego typu? Wynika to z wnioskowania typu . Ponieważ w treści funkcji pomnożono x przez 2 , typy x i 2 muszą być takie same. (Zasadniczo F # nie będzie domyślnie rzutował wartości na różne typy; musisz jawnie określić dowolne typecasty).

Jeśli chcesz utworzyć funkcję, która nie przyjmuje żadnych parametrów, jest to niewłaściwy sposób:

let hello =  // This is a value, not a function
    printfn "Hello world"

Właściwy sposób to zrobić:

let hello () =
    printfn "Hello world"

Ta funkcja hello ma unit -> unit typu unit -> unit , co wyjaśniono w rozdziale „Jednostka” .

Funkcje Curried kontra Tupled

Istnieją dwa sposoby definiowania funkcji z wieloma parametrami w F #, funkcjach Curled i Tupled.

let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature:  x:int * y:int -> int

Wszystkie funkcje zdefiniowane spoza F # (takie jak .NET Framework) są używane w F # z formularzem Tupled. Większość funkcji w podstawowych modułach F # jest używana z formą Curry.

Forma curry jest uważana za idiomatyczną F #, ponieważ pozwala na częściowe zastosowanie. Żaden z dwóch poniższych przykładów nie jest możliwy w przypadku formy Tupled.

let addTen = curriedAdd 10 // Signature: int -> int

// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10

Powodem tego jest to, że funkcja Curried, gdy jest wywoływana z jednym parametrem, zwraca funkcję. Witamy w programowaniu funkcjonalnym !!

let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature

Widać, że to dokładnie ten sam podpis.

Jednak w przypadku łączenia się z innym kodem .NET, jak w przypadku pisania bibliotek, ważne jest, aby zdefiniować funkcje za pomocą formularza Tupled.

Inlining

Inlining pozwala zamienić wywołanie funkcji na jej treść.

Jest to czasem przydatne ze względu na wydajność w krytycznej części kodu. Ale odpowiednikiem jest to, że twój zespół zajmie dużo miejsca, ponieważ treść funkcji jest duplikowana wszędzie tam, gdzie wystąpiło wywołanie. Musisz być ostrożny przy podejmowaniu decyzji, czy wstawić funkcję, czy nie.

Funkcję można wstawić za pomocą słowa kluczowego inline :

// inline indicate that we want to replace each call to sayHello with the body 
// of the function
let inline sayHello s1 =
    sprintf "Hello %s" s1

// Call to sayHello will be replaced with the body of the function
let s = sayHello "Foo"
// After inlining -> 
// let s = sprintf "Hello %s" "Foo"

printfn "%s" s
// Output
// "Hello Foo"

Kolejny przykład z wartością lokalną:

let inline addAndMulti num1 num2 =
    let add = num1 + num2
    add * 2

let i = addAndMulti 2 2
// After inlining ->
// let add = 2 + 2
// let i = add * 2

printfn "%i" i
// Output
// 8

Rura do przodu i do tyłu

Operatorzy rur służą do przekazywania parametrów do funkcji w prosty i elegancki sposób. Pozwala wyeliminować wartości pośrednie i ułatwić odczyt funkcji.

W języku F # istnieją dwa operatory potokowe:

  • Do przodu ( |> ): Przekazywanie parametrów od lewej do prawej

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     "Hello World" |> print
    
  • Wstecz ( <| ): Przekazywanie parametrów od prawej do lewej

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     print <| "Hello World"
    

Oto przykład bez operatorów rur:

// We want to create a sequence from 0 to 10 then:
// 1 Keep only even numbers
// 2 Multiply them by 2
// 3 Print the number

let mySeq = seq { 0..10 }
let filteredSeq = Seq.filter (fun c -> (c % 2) = 0) mySeq
let mappedSeq = Seq.map ((*) 2) filteredSeq
let finalSeq = Seq.map (sprintf "The value is %i.") mappedSeq

printfn "%A" finalSeq

Możemy skrócić poprzedni przykład i uczynić go czystszym za pomocą operatora rury do przodu:

// Using forward pipe, we can eliminate intermediate let binding
let finalSeq = 
    seq { 0..10 }
    |> Seq.filter (fun c -> (c % 2) = 0)
    |> Seq.map ((*) 2)
    |> Seq.map (sprintf "The value is %i.")

printfn "%A" finalSeq

Każdy wynik funkcji jest przekazywany jako parametr do następnej funkcji.

Jeśli chcesz przekazać wiele parametrów do operatora potoku, musisz dodać | dla każdego dodatkowego parametru i utwórz krotkę z parametrami. Natywny operator potoku F # obsługuje do trzech parametrów (|||> lub <|||).

let printPerson name age =
    printf "My name is %s, I'm %i years old" name age

("Foo", 20) ||> printPerson


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow