Wir setzen unsere Artikelserie zur funktionalen Programmierung in F # fort. Heute sprechen wir über die Assoziativität und Zusammensetzung von Funktionen und vergleichen die Zusammensetzung und die Pipeline. Schau unter die Katze!

Assoziativität und Zusammensetzung von Funktionen
Funktionsassoziativität
Angenommen, es gibt eine Reihe von Funktionen, die in einer Reihe geschrieben sind. In welcher Reihenfolge werden sie kombiniert?
Was bedeutet diese Funktion zum Beispiel?
let F xyz = xyz
Bedeutet dies, dass die Funktion y
auf das Argument z
angewendet und das Ergebnis dann an x
? Das heißt:
let F xyz = x (yz)
Oder wird die Funktion x
auf das Argument y
angewendet, wonach die als Ergebnis erhaltene Funktion mit dem Argument z
ausgewertet wird? Das heißt:
let F xyz = (xy) z
- Die zweite Option ist richtig.
- Die Verwendung von Funktionen hat Assoziativität hinterlassen .
xyz
bedeutet dasselbe wie (xy) z
.- Und
wxyz
ist gleich ((wx) y) z
. - Das sollte nicht toll aussehen.
- Wir haben bereits gesehen, wie eine Teilanwendung funktioniert.
- Wenn wir von
x
als einer Funktion mit zwei Parametern sprechen, dann ist (xy) z
das Ergebnis einer teilweisen Anwendung des ersten Parameters, gefolgt von der Übergabe des Arguments z
an die Zwischenfunktion.
Wenn Sie die richtige Assoziativität benötigen, können Sie Klammern oder Pipe verwenden. Die folgenden drei Einträge sind äquivalent:
let F xyz = x (yz) let F xyz = yz |> x // let F xyz = x <| yz //
Versuchen Sie als Übung, die Signaturen dieser Funktionen ohne echte Berechnung anzuzeigen.
Funktionszusammensetzung
Wir haben die Zusammensetzung von Funktionen mehrmals erwähnt, aber was bedeutet dieser Begriff wirklich? Auf den ersten Blick scheint es beängstigend, aber tatsächlich ist alles ganz einfach.
Angenommen, wir haben eine Funktion "f", die den Typ "T1" dem Typ "T2" zuordnet. Wir haben auch eine Funktion "g", die den Typ "T2" in den Typ "T3" konvertiert. Dann können wir den Ausgang von "f" und den Eingang von "g" verbinden und eine neue Funktion erstellen, die den Typ "T1" in den Typ "T3" konvertiert.

Zum Beispiel:
let f (x:int) = float x * 3.0 // f - int->float let g (x:float) = x > 4.0 // g - float->bool
Wir können eine neue Funktion "h" erstellen, die die Ausgabe von "f" als Eingabe für "g" verwendet.
let h (x:int) = let y = f(x) g(y) // g
Etwas kompakter:
let h (x:int) = g ( f(x) ) // h int->bool // h 1 h 2
So weit, so einfach. Das ist interessant, wir können eine neue Funktion "komponieren" definieren, die die Funktionen "f" und "g" übernimmt und sie kombiniert, ohne ihre Signaturen zu kennen.
let compose fgx = g ( f(x) )
Nach der Ausführung können Sie sehen, dass der Compiler korrekt entschieden hat, dass " f
" eine Funktion des generischen Typs 'a
für den generischen Typ 'b
ist und " g
" auf die Eingabe des Typs 'b
:
val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
(Beachten Sie, dass eine verallgemeinerte Zusammensetzung von Operationen nur möglich ist, weil jede Funktion genau einen Eingabeparameter und eine Ausgabe hat. Dieser Ansatz ist in nicht funktionalen Sprachen nicht möglich.)
Wie wir sehen können, wird diese Definition für den Operator " >>
" verwendet.
let (>>) fgx = g ( f(x) )
Dank dieser Definition können neue Funktionen auf der Grundlage vorhandener Funktionen mithilfe der Komposition erstellt werden.
let add1 x = x + 1 let times2 x = x * 2 let add1Times2 x = (>>) add1 times2 x // add1Times2 3
Explizite Aufnahmen sind sehr umständlich. Sie können die Verwendung jedoch verständlicher gestalten.
Erstens können Sie den Parameter x
, und die Komposition gibt eine Teilanwendung zurück.
let add1Times2 = (>>) add1 times2
Zweitens, weil >>
ist ein binärer Operator, den Sie in die Mitte stellen können.
let add1Times2 = add1 >> times2
Durch die Verwendung von Komposition wird der Code sauberer und klarer.
let add1 x = x + 1 let times2 x = x * 2 // let add1Times2 x = times2(add1 x) // let add1Times2 = add1 >> times2
Verwenden des Kompositionsoperators in der Praxis
Der Kompositionsoperator hat (wie alle Infix-Operatoren) eine niedrigere Priorität als reguläre Funktionen. Dies bedeutet, dass die in der Komposition verwendeten Funktionen Argumente ohne Klammern haben können.
Wenn beispielsweise die Funktionen "Hinzufügen" und "Zeiten" Parameter haben, können sie während der Komposition übergeben werden.
let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 let add5Times3 = add 5 >> times 3 // add5Times3 1
Solange die entsprechenden Ein- und Ausgänge der Funktionen übereinstimmen, können die Funktionen beliebige Werte verwenden. Betrachten Sie beispielsweise den folgenden Code, der eine Funktion zweimal ausführt:
let twice f = f >> f // ('a -> 'a) -> ('a -> 'a)
Beachten Sie, dass der Compiler daraus geschlossen hat, dass " f
" Werte desselben Typs akzeptiert und zurückgibt.
Betrachten Sie nun die Funktion " +
". Wie wir zuvor gesehen haben, ist die Eingabe int
, aber die Ausgabe ist tatsächlich (int->int)
. Somit kann " +
" in " twice
" verwendet werden. Daher können Sie schreiben:
let add1 = (+) 1 // (int -> int) let add1Twice = twice add1 // (int -> int) // add1Twice 9
Auf der anderen Seite können Sie nicht schreiben:
let addThenMultiply = (+) >> (*)
Da die Eingabe "*" eine int
Funktion sein muss, keine int->int
Funktion (die die Ausgabe der Addition ist).
Wenn Sie jedoch die erste Funktion so korrigieren, dass nur int
wird, funktioniert alles:
let add1ThenMultiply = (+) 1 >> (*) // (+) 1 (int -> int) 'int' // add1ThenMultiply 2 7
Die Komposition kann bei Bedarf auch in umgekehrter Reihenfolge mit " <<
" durchgeführt werden:
let times2Add1 = add 1 << times 2 times2Add1 3
Die umgekehrte Komposition wird hauptsächlich verwendet, um den Code eher englisch ("englisch") zu machen. Zum Beispiel:
let myList = [] myList |> List.isEmpty |> not // myList |> (not << List.isEmpty) //
Zusammensetzung vs. Förderer
Sie können durch den kleinen Unterschied zwischen der Zusammensetzung und dem Förderer verwirrt sein, wie Sie können sehr ähnlich aussehen.
Schauen Sie sich zunächst die Definition einer Pipeline an:
let (|>) xf = fx
All dies ermöglicht es Ihnen, die Argumente von Funktionen davor und nicht danach zu setzen. Das ist alles. Wenn die Funktion mehrere Parameter hat, sollte die Eingabe der letzte Parameter sein (im aktuellen Parametersatz und überhaupt nicht). Ein früher gesehenes Beispiel:
let doSomething xyz = x+y+z doSomething 1 2 3 // 3 |> doSomething 1 2 //
Die Zusammensetzung ist nicht dieselbe und kann kein Ersatz für ein Rohr sein. Im folgenden Beispiel ist auch die Nummer 3 keine Funktion, sodass die "Ausgabe" nicht an doSomething
:
3 >> doSomething 1 2 // // f >> g g(f(x)) : doSomething 1 2 ( 3(x) ) // 3 ! // error FS0001: This expression was expected to have type 'a->'b // but here has type int
Der Compiler beschwert sich, dass der Wert "3" eine Art Funktion 'a->'b
.
Vergleichen Sie dies mit der Definition der Komposition, die 3 Argumente akzeptiert, wobei die ersten beiden Funktionen sein sollten.
let (>>) fgx = g ( f(x) ) let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2
Versuche, die Pipeline anstelle der Komposition zu verwenden, führen zu einem Kompilierungsfehler. Im folgenden Beispiel ist " add 1
" die (Teil-) Funktion int->int
, die nicht als zweiter Parameter für " times 2
" verwendet werden kann.
let add1Times2 = add 1 |> times 2 // // x |> f f(x) : let add1Times2 = times 2 (add 1) // add1 'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int'
Der Compiler beschwert sich, dass " times 2
" den Parameter int->int
akzeptieren muss, d.h. sei eine Funktion (int->int)->'a
.
Zusätzliche Ressourcen
Es gibt viele Tutorials für F #, einschließlich Materialien für diejenigen, die mit C # oder Java-Erfahrung kommen. Die folgenden Links können hilfreich sein, wenn Sie tiefer in F # einsteigen:
Es werden auch verschiedene andere Möglichkeiten beschrieben , um mit dem Lernen von F # zu beginnen .
Schließlich ist die F # Community sehr anfängerfreundlich. Bei Slack gibt es einen sehr aktiven Chat, der von der F # Software Foundation unterstützt wird, mit Anfängerräumen, an denen Sie frei teilnehmen können . Wir empfehlen Ihnen dringend, dies zu tun!
Vergessen Sie nicht, die Seite der russischsprachigen Community F # zu besuchen! Wenn Sie Fragen zum Erlernen einer Sprache haben, diskutieren wir diese gerne in Chatrooms:
Über Übersetzungsautoren
Übersetzt von @kleidemos
Übersetzungs- und redaktionelle Änderungen wurden durch die Bemühungen der russischsprachigen Community von F # -Entwicklern vorgenommen . Wir danken auch @schvepsss und @shwars für die Vorbereitung dieses Artikels zur Veröffentlichung.