Zoeken…
Functies van meer dan één parameter
In F # hebben alle functies exact één parameter . Dit lijkt een vreemde verklaring, omdat het triviaal eenvoudig is om meer dan één parameter in een functieverklaring te declareren:
let add x y = x + y
Maar als u die functieverklaring typt in de interactieve F # -interpreter, ziet u dat de typeaanduiding is:
val add : x:int -> y:int -> int
Zonder de parameternamen is die handtekening int -> int -> int
. De ->
operator is recht-associatief, wat betekent dat deze handtekening gelijk is aan int -> (int -> int)
. Met andere woorden, add
is een functie die een neemt int
parameter, en een functie die draait terug int
en keert terug int
. Probeer het:
let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5
U kunt echter ook een functie als add
op een "conventionelere" manier aanroepen en deze direct twee parameters doorgeven, en het zal werken zoals u zou verwachten:
let three = add 1 2
// val three : int = 3
Dit is van toepassing op functies met zoveel parameters als u wilt:
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
Deze methode om multi-parameterfuncties te beschouwen als functies die één parameter aannemen en nieuwe functies retourneren (die op hun beurt één parameter kunnen aannemen en nieuwe functies retourneren, totdat u de laatste functie bereikt die de laatste parameter neemt en uiteindelijk een resultaat retourneert) wordt currying genoemd , ter ere van wiskundige Haskell Curry, die beroemd is om het concept te ontwikkelen. (Het is bedacht door iemand anders, maar Curry krijgt terecht het meeste krediet.)
Dit concept wordt overal in F # gebruikt en je zult er bekend mee willen zijn.
Basis van functies
De meeste functies in F # zijn gemaakt met de let
syntaxis:
let timesTwo x = x * 2
Dit definieert een functie met de naam timesTwo
die een enkele parameter x
. Als u een interactieve F # -sessie fsharpi
( fsharpi
op OS X en Linux, fsi.exe
op Windows) en die functie in plakt (en de ;;
toevoegt die fsharpi
vertelt om de code die u zojuist hebt getypt te evalueren), zult u zien dat het antwoordt met:
val timesTwo : x:int -> int
Dit betekent dat timesTwo
een functie is die een enkele parameter x
van het type int
gebruikt en een int
retourneert. Functiehandtekeningen worden vaak geschreven zonder de parameternamen, dus u zult dit functietype vaak zien als int -> int
.
Maar wacht! Hoe wist F # dat x
een gehele parameter was, omdat je het type nooit hebt opgegeven? Dat komt door type gevolgtrekking . Omdat u in de hoofdtekst x
met 2
hebt vermenigvuldigd, moeten de typen x
en 2
hetzelfde zijn. (Als algemene regel zal F # niet impliciet waarden naar verschillende typen casten; u moet expliciet elke gewenste typecast specificeren).
Als u een functie wilt maken die geen parameters nodig heeft, is dit de verkeerde manier om dit te doen:
let hello = // This is a value, not a function
printfn "Hello world"
De juiste manier om dit te doen is:
let hello () =
printfn "Hello world"
Deze hello
functie heeft het type unit -> unit
, wat wordt uitgelegd in het type "eenheid" .
Curried vs Tupled Functions
Er zijn twee manieren om functies te definiëren met meerdere parameters in F #, Curried-functies en Tupled-functies.
let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature: x:int * y:int -> int
Alle functies gedefinieerd van buiten F # (zoals het .NET framework) worden gebruikt in F # met het Tupled-formulier. De meeste functies in F # kernmodules worden gebruikt met Curried-vorm.
De Curried-vorm wordt als idiomatische F # beschouwd, omdat deze gedeeltelijke toepassing mogelijk maakt. Geen van de volgende twee voorbeelden zijn mogelijk met de Tupled-vorm.
let addTen = curriedAdd 10 // Signature: int -> int
// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10
De reden daarachter is dat de functie Curried, wanneer deze met één parameter wordt aangeroepen, een functie retourneert. Welkom bij functioneel programmeren !!
let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature
Je kunt zien dat het precies dezelfde handtekening is.
Wanneer u echter interfacet met andere .NET-code, zoals bij het schrijven van bibliotheken, is het belangrijk om functies te definiëren met behulp van het Tupled-formulier.
inlining
Met Inlining kunt u een oproep naar een functie vervangen door de hoofdtekst van de functie.
Dit is soms handig om prestatieredenen op een kritisch deel van de code. Maar de tegenpartij is dat uw vergadering veel ruimte in beslag neemt, omdat de hoofdtekst van de functie overal wordt gedupliceerd waar een oproep plaatsvond. Je moet voorzichtig zijn bij het beslissen of je een functie wilt inline of niet.
Een functie kan worden uitgelijnd met het inline
trefwoord:
// 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"
Een ander voorbeeld met lokale waarde:
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
Pijp vooruit en achteruit
Pijpexploitanten worden gebruikt om parameters op een eenvoudige en elegante manier aan een functie door te geven. Hiermee kunt u tussenliggende waarden elimineren en functieoproepen gemakkelijker leesbaar maken.
In F # zijn er twee pijpexploitanten:
Vooruit (
|>
): parameters van links naar rechts doorgevenlet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function "Hello World" |> print
Achteruit (
<|
): parameters van rechts naar links doorgevenlet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function print <| "Hello World"
Hier is een voorbeeld zonder pijpexploitanten:
// 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
We kunnen het vorige voorbeeld inkorten en schoner maken met de voorwaartse pijpoperator:
// 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
Elk functieresultaat wordt als parameter doorgegeven aan de volgende functie.
Als u meerdere parameters aan de pijpexploitant wilt doorgeven, moet u een |
voor elke aanvullende parameter en maak een Tuple met de parameters. Native F # pipe-operator ondersteunt maximaal drie parameters (|||> of <|||).
let printPerson name age =
printf "My name is %s, I'm %i years old" name age
("Foo", 20) ||> printPerson