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 doorgeven

     let 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 doorgeven

     let 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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow