Suche…


Funktionen von mehr als einem Parameter

In F # haben alle Funktionen genau einen Parameter . Dies scheint eine merkwürdige Anweisung zu sein, da es einfach ist, mehr als einen Parameter in einer Funktionsdeklaration zu deklarieren:

let add x y = x + y

Wenn Sie diese Funktionsdeklaration in den interaktiven F # -Interpreter eingeben, werden Sie feststellen, dass die Typensignatur wie folgt lautet:

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

Ohne die Parameternamen lautet diese Signatur int -> int -> int . Der Operator -> ist rechtsassoziativ, int -> (int -> int) diese Signatur entspricht int -> (int -> int) . Mit anderen Worten, add Sie ist eine Funktion , die man nimmt int Parameter und gibt eine Funktion , die man nimmt int und kehrt int . Versuch es:

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

Sie können jedoch auch eine Funktion wie " add auf eine "konventionellere" Weise aufrufen, indem Sie direkt zwei Parameter übergeben, und es funktioniert wie erwartet:

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

Dies gilt für Funktionen mit beliebig vielen Parametern:

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

Diese Methode, Multi-Parameter-Funktionen als Funktionen zu betrachten, die einen Parameter annehmen und neue Funktionen zurückgeben (die wiederum einen Parameter und neue Funktionen zurückgeben können, bis Sie die letzte Funktion erreichen, die den endgültigen Parameter übernimmt und schließlich ein Ergebnis zurückgibt.) wird Curry genannt , zu Ehren des Mathematikers Haskell Curry, der für die Entwicklung des Konzepts berühmt ist. (Es wurde von jemand anderem erfunden, aber Curry verdient zu Recht den größten Preis dafür.)

Dieses Konzept wird in F # verwendet, und Sie sollten sich damit auskennen.

Grundlagen der Funktionen

Die meisten Funktionen in F # werden mit der let Syntax erstellt:

let timesTwo x = x * 2

Dies definiert eine Funktion namens timesTwo , die einen einzelnen Parameter x . Wenn Sie eine interaktive F # fsharpi ( fsharpi unter OS X und Linux, fsi.exe unter Windows) fsi.exe und diese Funktion einfügen (und das ;; hinzufügen, das fsharpi , den gerade eingegebenen Code auszuwerten), wird dies fsharpi antwortet mit:

val timesTwo : x:int -> int

Dies bedeutet, dass timesTwo eine Funktion ist, die einen einzelnen Parameter x vom Typ int übernimmt und ein int zurückgibt. Funktionssignaturen werden oft ohne die Parameternamen geschrieben. int -> int dieser Funktionstyp häufig als int -> int .

Aber warte! Woher wusste F #, dass x ein Integer-Parameter war, da Sie den Typ nie angegeben haben? Das liegt am Typ Inferenz . Da Sie im Funktionshauptteil x mit 2 multipliziert haben, müssen die Typen von x und 2 gleich sein. (Generell gilt, dass F # Werte nicht implizit in andere Typen umwandelt; Sie müssen explizit alle gewünschten Typumwandlungen angeben.)

Wenn Sie eine Funktion erstellen möchten, für die keine Parameter erforderlich sind, ist dies der falsche Weg:

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

Der richtige Weg, es zu tun, ist:

let hello () =
    printfn "Hello world"

Diese hello Funktion hat den Typ unit -> unit , was im Typ "Einheit" erläutert wird.

Curried vs getippte Funktionen

Es gibt zwei Möglichkeiten, Funktionen mit mehreren Parametern in F #, Curried-Funktionen und getopften Funktionen zu definieren.

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 Funktionen, die von außerhalb von F # definiert wurden (z. B. das .NET-Framework), werden in F # mit dem getippten Formular verwendet. Die meisten Funktionen in F # -Kernmodulen werden in Curried-Form verwendet.

Die Curry-Form wird als idiomatische F # betrachtet, da sie eine teilweise Anwendung erlaubt. Keines der folgenden zwei Beispiele ist mit der getupften Form möglich.

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

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

Der Grund dafür ist, dass die Funktion Curried eine Funktion zurückgibt, wenn sie mit einem Parameter aufgerufen wird. Willkommen bei der funktionalen Programmierung!

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

Sie können sehen, dass es genau dieselbe Signatur ist.

Wenn Sie jedoch mit anderem .NET-Code kommunizieren, wie beim Schreiben von Bibliotheken, ist es wichtig, Funktionen mithilfe des getupten Formulars zu definieren.

Inlining

Durch Inlining können Sie einen Aufruf einer Funktion durch den Hauptteil der Funktion ersetzen.

Dies ist manchmal aus Leistungsgründen im kritischen Teil des Codes hilfreich. Das Gegenstück ist jedoch, dass der Aufbau Ihrer Assembly viel Platz beansprucht, da der Rumpf der Funktion überall dort dupliziert wird, wo ein Aufruf stattgefunden hat. Sie müssen vorsichtig sein, wenn Sie entscheiden, ob Sie eine Funktion integrieren möchten oder nicht.

Eine Funktion kann mit dem inline Schlüsselwort eingebettet werden:

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

Ein weiteres Beispiel mit lokalem Wert:

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

Pipe vorwärts und rückwärts

Über Pipe-Operatoren werden Parameter auf einfache und elegante Weise an eine Funktion übergeben. Dadurch können Zwischenwerte eliminiert und Funktionsaufrufe leichter lesbar gemacht werden.

In F # gibt es zwei Pipe-Operatoren:

  • Forward ( |> ): Parameter werden von links nach rechts übergeben

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     "Hello World" |> print
    
  • Rückwärts ( <| ): Parameter von rechts nach links übergeben

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

Hier ist ein Beispiel ohne Pipe-Operator:

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

Wir können das vorherige Beispiel verkürzen und mit dem Forward-Pipe-Operator sauberer gestalten:

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

Jedes Funktionsergebnis wird als Parameter an die nächste Funktion übergeben.

Wenn Sie mehrere Parameter an den Pipe-Operator übergeben möchten, müssen Sie ein | hinzufügen für jeden zusätzlichen Parameter und erstellen Sie ein Tuple mit den Parametern. Der native F # -Pipe-Operator unterstützt bis zu drei Parameter (|||> oder <|||).

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow