Funktionen (F#)

Funktionen sind in jeder Programmiersprache die grundlegende Einheit der Programmausführung. Wie Funktionen anderer Sprachen weist eine F#-Funktion einen Namen auf, sie kann über Parameter verfügen und Argumente akzeptieren, und sie verfügt über einen Funktionsrumpf. F# unterstützt auch Konstrukte der funktionalen Programmierung. Hierzu zählen beispielsweise das Behandeln von Funktionen als Werte, das Verwenden unbenannter Funktionen in Ausdrücken, das Zusammensetzen von Funktionen zum Bilden neuer Funktionen, Curry-Funktionen sowie die implizite Definition von Funktionen durch die partielle Anwendung von Funktionsargumenten.

Funktionen werden mit dem let-Schlüsselwort oder, wenn die Funktion rekursiv ist, mit dem let rec Schlüsselwort definiert.

// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body

Hinweise

Der function-name ist ein Bezeichner, der die Funktion darstellt. Die parameter-list besteht aus aufeinander folgenden Parametern, die durch Leerzeichen getrennt sind. Sie können einen expliziten Typ für jeden Parameter angeben, wie im Abschnitt Parameter beschrieben. Wenn Sie keinen bestimmten Argumenttyp angeben, versucht der Compiler, den Typ aus dem Funktionsrumpf abzuleiten. Der function-body besteht aus einem Ausdruck. Der Ausdruck, der den Funktionsrumpf bildet, ist in der Regel ein zusammengesetzter Ausdruck, der aus einer Reihe von Ausdrücken besteht, die mit einem abschließenden Ausdruck enden, bei dem es sich um den Rückgabewert handelt. Der return-type ist ein Doppelpunkt, gefolgt von einem Typ, und er ist optional. Wenn Sie den Typ des Rückgabewerts nicht explizit angeben, bestimmt der Compiler den Rückgabetyp anhand des abschließenden Ausdrucks.

Eine einfache Funktionsdefinition lautet etwa folgendermaßen:

let f x = x + 1

Im vorherigen Beispiel ist f der Funktionsname, x ist das Argument mit dem Typ int, x + 1 ist der Funktionsrumpf, und der Rückgabewert ist vom Typ int.

Der Inlinespezifizierer informiert den Compiler, dass es sich um eine kleine Funktion handelt und dass der Code für die Funktion in den Rumpf des Aufrufers integriert werden kann.

Umfang

Die Wiederverwendung des Namens für einen Wert oder eine Funktion ist auf jeder Gültigkeitsbereichsebene außer dem Gültigkeitsbereich des Moduls zulässig. Wenn Sie einen Namen wiederverwenden, führt der später deklarierte Name Shadowing für den früher deklarierten Namen aus. Auf der obersten Gültigkeitsbereichsebene in einem Modul müssen Namen jedoch eindeutig sein. Beispielsweise generiert der folgende Code einen Fehler, wenn er im Modulgültigkeitsbereich vorhanden ist, jedoch nicht, wenn er sich in einer Funktion befindet:

let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []  
let function1 =
   let list1 = [1; 2; 3]
   let list1 = []
   list1

Jedoch ist der folgende Code auf jeder Gültigkeitsbereichsebene zulässig:

let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
   let list1 = [1; 5; 10]  
   x + List.sum list1

Parameter

Namen von Parametern werden nach dem Funktionsnamen aufgeführt. Sie können einen Typ für einen Parameter angeben, wie im folgenden Beispiel gezeigt:

let f (x : int) = x + 1

Wenn Sie einen Typ angeben, folgt er auf den Namen des Parameters, und er wird durch einen Doppelpunkt vom Namen getrennt. Wenn Sie den Typ für den Parameter weglassen, wird der Parametertyp vom Compiler abgeleitet. Beispielsweise wird in der folgenden Funktionsdefinition das Argument x als Argument vom Typ int abgeleitet, weil 1 vom Typ int ist.

let f x = x + 1

Der Compiler versucht jedoch, die Funktion so generisch wie möglich festzulegen. Beachten Sie z. B. folgenden Code:

let f x = (x, x)

Die Funktion erstellt ein Tupel aus einem Argument beliebigen Typs. Da der Typ nicht angegeben ist, kann die Funktion mit jedem Argumenttyp verwendet werden. Weitere Informationen finden Sie unter Automatische Verallgemeinerung (F#).

Weitere Informationen zu Parametern finden Sie unter Parameter und Argumente (F#).

Funktionsrümpfe

Ein Funktionsrumpf kann Definitionen von lokalen Variablen und Funktionen enthalten. Der Gültigkeitsbereich dieser Variablen und Funktionen ist auf den Rumpf der aktuellen Funktion begrenzt. Wenn Sie die Option für die einfache Syntax aktiviert haben, müssen Sie mithilfe des Einzugs angeben, dass sich eine Definition in einem Funktionsrumpf befindet, wie im folgenden Beispiel gezeigt:

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

Weitere Informationen finden Sie unter Richtlinien für das Formatieren von Code (F#) und Ausführliche Syntax (F#).

Rückgabewerte

Der Compiler bestimmt den Rückgabewert und -typ mithilfe des abschließenden Ausdrucks in einem Funktionsrumpf. Der Compiler kann den Typ des abschließenden Ausdrucks von vorherigen Ausdrücken ableiten. In der im vorherigen Abschnitt gezeigten Funktion cylinderVolume wird der Typ von pi anhand des Typs des Literals 3.14159 als float bestimmt. Der Compiler bestimmt den Typ des Ausdrucks h * pi * r * r anhand des Typs von pi als float. Daher ist der allgemeine Rückgabetyp der Funktion float.

Wenn Sie den Rückgabewert explizit angeben möchten, schreiben Sie folgenden Code:


let cylinderVolume radius length : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius

Wenn obiger Code geschrieben wurde, wendet der Compiler float auf die gesamte Funktion an. Wenn der Typ auch auf die Parametertypen angewendet werden soll, verwenden Sie folgenden Code:

let cylinderVolume (radius : float) (length : float) : float

Eine Funktion gibt immer genau einen Wert zurück. Wenn eine Funktion keinen tatsächlichen Wert zurückgibt, kann der unit-Wert zurückgegeben werden. Sie können mehrere Datenteile auf mehrere Arten zurückgeben. Eine Möglichkeit besteht darin, einen Tupelwert zurückzugeben. Wenn eine Funktion einen Tupelwert zurückgibt, können Sie ein Tupelmuster in einer let-Bindung verwenden, um die Elemente des Tupels mehr als einem Wert zuzuweisen. Dies wird im folgenden Code veranschaulicht.

let firstAndLast list1 =
    (List.head list1, List.head (List.rev list1))

let (first, last) = firstAndLast [ 1; 2; 3 ]
printfn "First: %d; Last: %d" first last

Die Ausgabe des obigen Codes lautet wie folgt:

  

Eine weitere Möglichkeit, mehrere Daten zurückzugeben, ist die Verwendung von Referenzzellen, und eine dritte Möglichkeit ist die Verwendung von byref-Parametern. Weitere Informationen und Beispiele finden Sie unter Referenzzellen (F#) und Parameter und Argumente (F#).

Aufrufen einer Funktion

Sie rufen Funktionen auf, indem Sie den Funktionsnamen, danach ein Leerzeichen und anschließend ggf. durch Leerzeichen getrennte Argumente angeben. Beispielsweise schreiben Sie folgenden Code, um die Funktion cylinderVolume aufzurufen und dem Wert vol das Ergebnis zuzuweisen:

let vol = cylinderVolume 2.0 3.0

Wenn eine Funktion ein Tupel als einzelnen Parameter akzeptiert, endet ein Funktionsaufruf mit einer Liste in Klammern, die in anderen Sprachen wie eine Argumentliste aussieht. Der Platz zwischen dem Funktionsnamen und der öffnenden Klammer kann ausgelassen werden. Beispielsweise ist das Folgende die cylinderVolume-Funktion mit einem Tupel als Parameter.

let cylinderVolume(radius, length) =
    let pi = 3.14159
    length * pi * radius * radius

Mit einem Tupel als Argument sieht der Aufruf der Funktion wie folgt aus.

let vol = cylinderVolume(2.0, 3.0)

Wenn eine Funktion keine Parameter akzeptiert, geben Sie den unit-Wert () wie in der folgenden Codezeile als Argument an.

initializeApp()

Der Name einer Funktion allein ist nur ein Funktionswert. Wenn Sie die Klammern, die den unit-Wert angeben, auslassen, wird lediglich auf die Funktion verwiesen, sie wird nicht aufgerufen.

Partielle Anwendung von Argumenten

Wenn Sie eine geringere Anzahl von Argumenten als angegeben bereitstellen, erstellen Sie eine neue Funktion, die die restlichen Argumente erwartet. Dieses Verfahren zum Behandeln von Argumenten ist eine Besonderheit funktionaler Programmiersprachen wie F#, die als Currying bezeichnet wird. Angenommen, Sie arbeiten mit zwei Rohrgrößen: ein Radius von 2.0 und ein Radius von 3.0. Sie können Funktionen erstellen, die das Rohrvolumen wie folgt bestimmen:

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining
// argument:

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

Anschließend stellen Sie je nach Bedarf das zusätzliche Argument für verschiedene Rohrlängen der beiden Rohrgrößen bereit:

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

Rekursive Funktionen

Rekursive Funktionen sind Funktionen, die sich selbst aufrufen. Sie erfordern die Angabe des rec-Schlüsselworts nach dem let-Schlüsselwort. Rufen Sie die rekursive Funktion im Rumpf der Funktion so auf, wie Sie jede andere Funktion aufrufen. Die folgende rekursive Funktion berechnet die n-te Fibonacci-Zahl. Die Fibonacci-Zahlenfolge ist seit dem Altertum bekannt. Jede Zahl in dieser Folge ist die Summe der vorherigen zwei Zahlen in der Folge.

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

Einige rekursive Funktionen können einen Überlauf des Programmstapels verursachen oder ineffizient ausgeführt werden, wenn Sie sie ohne die erforderliche Sorgfalt und ohne Kenntnis spezieller Verfahren, z. B. die Verwendung von Akkumulatoren und Fortsetzungen, schreiben.

Funktionswerte

In F# gelten alle Funktionen als Werte. Funktionen werden sogar als Funktionswerte bezeichnet. Da Funktionen Werte sind, können sie als Argumente für andere Funktionen oder in anderen Kontexten, in denen Werte verwendet werden, eingesetzt werden. Es folgt ein Beispiel für eine Funktion, die einen Funktionswert als Argument akzeptiert:

let apply1 (transform : int -> int ) y = transform y

Sie geben den Typ eines Funktionswerts mit dem ->-Token an. Auf der linken Seite dieses Tokens befindet sich der Typ des Arguments und auf der rechten Seite der Rückgabewert. Im vorherigen Beispiel ist apply1 eine Funktion, die die Funktion transform als Argument akzeptiert, wobei transform eine Funktion ist, die eine ganze Zahl akzeptiert und eine andere ganze Zahl zurückgibt. Im folgenden Code wird die Verwendung von apply1 veranschaulicht:

let increment x = x + 1

let result1 = apply1 increment 100

Nach der Ausführung des vorangehenden Codes ist der Wert von result 101.

Mehrere Argumente werden durch aufeinander folgende ->-Token getrennt, wie im folgenden Beispiel gezeigt:

let apply2 ( f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

Das Ergebnis ist 200.

Lambda-Ausdrücke

Ein Lambda-Ausdruck ist eine unbenannte Funktion. In den vorherigen Beispielen können Sie Lambda-Ausdrücke wie folgt verwenden, statt die benannten Funktionen increment und mul zu definieren:

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y ) 10 20

Sie definieren Lambda-Ausdrücke mit dem fun-Schlüsselwort. Ein Lambda-Ausdruck ähnelt einer Funktionsdefinition, mit dem Unterschied, dass statt des =-Tokens das ->-Token verwendet wird, um die Argumentliste vom Funktionsrumpf zu trennen. Wie in einer regulären Funktionsdefinition können die Argumenttypen abgeleitet oder explizit angegeben werden, und der Rückgabetyp des Lambda-Ausdrucks wird vom Typ des letzten Ausdrucks im Rumpf abgeleitet. Weitere Informationen finden Sie unter Lambda-Ausdrücke: Das fun-Schlüsselwort (F#).

Funktionszusammensetzung und Pipelinefunktionen

Funktionen in F# können aus anderen Funktionen zusammengesetzt sein. Die Zusammensetzung der beiden Funktionen function1 und function2 ist eine weitere Funktion, die die Anwendung von function1 und die anschließende Anwendung von function2 darstellt:

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

Das Ergebnis ist 202.

Pipelinefunktionen ermöglichen die Verkettung von Funktionsaufrufen als aufeinander folgende Vorgänge. Pipelinefunktionen werden wie folgt ausgeführt:

let result = 100 |> function1 |> function2

Das Ergebnis ist erneut 202.

Siehe auch

Weitere Ressourcen

Werte (F#)

F#-Sprachreferenz

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

Mai 2010

Codebeispiel im Abschnitt "Parameter" wurde korrigiert.

Korrektur inhaltlicher Fehler.

April 2011

Klärende Informationen zu Parametern und Rückgabewerten wurden hinzugefügt.