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ögerlet 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änsterlet 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