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 übergebenlet 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 übergebenlet 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