Sök…


Funktioner för mer än en parameter

I F # tar alla funktioner exakt en parameter . Detta verkar vara ett konstigt uttalande, eftersom det är trivialt enkelt att deklarera mer än en parameter i en funktionsdeklaration:

let add x y = x + y

Men om du skriver den funktionsdeklarationen i den interaktiva F # -tolkaren ser du att dess typsignatur är:

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

Utan parameternamn är signaturen int -> int -> int . Operatören -> är rätt associerande, vilket innebär att denna signatur motsvarar int -> (int -> int) . Med andra ord är add en funktion som tar en int parameter och returnerar en funktion som tar en int och returnerar int . Försök:

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

Du kan emellertid också ringa en funktion som add på ett mer "konventionellt" sätt, direkt överföra det två parametrar, och det kommer att fungera som du kan förvänta dig:

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

Detta gäller för funktioner med så många parametrar du vill:

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

Denna metod för att tänka på flerparameterfunktioner som funktioner som tar en parameter och returnerar nya funktioner (som i sin tur kan ta en parameter och returnera nya funktioner, tills du når den slutliga funktionen som tar den slutliga parametern och slutligen returnerar ett resultat) kallas currying , för att hedra matematikern Haskell Curry, som är känd för att utveckla konceptet. (Det uppfanns av någon annan, men Curry får förtjänst det mesta av krediterna för det.)

Det här konceptet används i hela F #, och du vill bli bekant med det.

Grunderna i funktioner

De flesta funktioner i F # skapas med let syntax:

let timesTwo x = x * 2

Detta definierar en funktion med namnet timesTwo som tar en enda parameter x . Om du kör en interaktiv F # -session ( fsharpi på OS X och Linux, fsi.exe på Windows) och klistrar in den funktionen (och lägger till ;; som säger fsharpi att utvärdera koden du just skrev), ser du att den svarar med:

val timesTwo : x:int -> int

Detta betyder att timesTwo är en funktion som tar en enda parameter x av typen int och returnerar en int . Funktionssignaturer skrivs ofta utan parameternamn, så du ser ofta den här funktionstypen som int -> int .

Men vänta! Hur visste F # att x var en heltalsparameter, eftersom du aldrig specificerade typen? Det beror på typinferensen . Eftersom i funktionen kroppen, multiplicerat du x med 2 , vilka typer av x och 2 måste vara densamma. (Som en allmän regel kommer F # inte implicit att kasta värden till olika typer; du måste uttryckligen ange vilka typsnitt du vill).

Om du vill skapa en funktion som inte tar några parametrar är det fel sätt att göra det:

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

Det rätta sättet att göra det är:

let hello () =
    printfn "Hello world"

Denna hello har typen unit -> unit , vilket förklaras i typen "enhet" .

Curried vs Tupled-funktioner

Det finns två sätt att definiera funktioner med flera parametrar i F #, Curried-funktioner och Tupled-funktioner.

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

Alla funktioner definierade utanför F # (som. NET-ramverket) används i F # med Tupled-formen. De flesta funktioner i F # -kärnmoduler används med Curried-form.

Curried-formen betraktas som idiomatisk F #, eftersom den möjliggör partiell applikation. Inget av följande två exempel är möjliga med Tupled-formen.

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

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

Anledningen bakom detta är att Curried-funktionen, när den anropas med en parameter, returnerar en funktion. Välkommen till funktionell programmering !!

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

Du kan se att det är exakt samma signatur.

Men när man gränsar till annan .NET-kod, som när man skriver bibliotek, är det viktigt att definiera funktioner med hjälp av formen Tupled.

inlining

Inlining låter dig ersätta ett samtal till en funktion med funktionen.

Detta är ibland användbart av prestandaskäl på en kritisk del av koden. Men motsvarigheten är att din montering tar mycket utrymme eftersom funktionen består av överallt där ett samtal inträffade. Du måste vara försiktig när du bestämmer dig för att inline en funktion eller inte.

En funktion kan inriktas med inline nyckelordet:

// 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"

Ett annat exempel med lokalt värde:

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

Rör framåt och bakåt

Röroperatörer används för att skicka parametrar till en funktion på ett enkelt och elegant sätt. Det gör det möjligt att eliminera mellanvärden och göra funktionssamtal lättare att läsa.

I F # finns det två röroperatörer:

  • Framåt ( |> ): Vidarebefordra parametrar från vänster till höger

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     "Hello World" |> print
    
  • Bakåt ( <| ): Vidarebefordra parametrar från höger till vänster

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

Här är ett exempel utan röroperatörer:

// 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

Vi kan förkorta föregående exempel och göra det renare med den främre röroperatören:

// 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

Varje funktionsresultat skickas som en parameter till nästa funktion.

Om du vill överföra flera parametrar till röroperatören måste du lägga till en | för varje ytterligare parameter och skapa en Tuple med parametrarna. Native F # pipe operator stöder upp till tre parametrar (|||> eller <|||).

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow