Funktionen in F#

In der Funktionalen Programmierung (FP) dreht sich alles um Funktionen.
Jede Funktion hat Parameter und Rückgabewert.
Davon kann man jeweils 0 bis (fast-)beliebig haben.

Es geht im folgenden um: Funktionen, Parameter, Rückgabewert, Tupel, Signaturen

Ein Parameter und ein Rückgabewert ist am einfachsten zu verstehen.

1: 
let sqr (x) = x * x     // x wird quadriert

Zwei Parameter und ein Rückgabewert

1: 
let add (x,y) = x + y   // x und y wird addiert

Hier ist (x,y) ein sogenanntes Tupel, das zwei Werte verbindet.

So ist es möglich mehr als einen Rückgabewert zu verwenden.

Jeder Teilwert eines Tupels kann einen beliebigen Type haben.

Einige nette Wortspielereien zur Auflockerung. Man muss das nicht auswendig wissen. Und nicht verwechseln mit Tribbles 😉

“Ein 2-Tupel nennt man auch geordnetes Paar,

ein 3-Tupel auch Tripel,

ein 4-Tupel auch Quadrupel,

ein 5-Tupel auch Quintupel und so fort.

Das 0-Tupel heißt leeres Tupel und wird durch () notiert.”

http://de.wikipedia.org/wiki/Tupel

Und das ist jetzt ganz wichtig und kann sonst ein Stolperstein sein beim Erlernen von F#. Darum aufgepasst!
Ja und da ist das Verständnis etwas anders als in C# oder C++, aber vollkommen logisch.
Die Klammer gehört zum Tupel und nicht etwa zum “Funktions-Parameter-Rupf”. Man kennt es ja eher so von C# oder C++, was genau den selben Code ergibt wie oben.

1: 
let add(x,y) = x + y    // kein space zwischen "add" und "("

Aber beim obigen ist es klarer als Tupel zu erkennen.
Wichtig ist den Unterschied zwischen Tupel und dem “Funktionsrumpf” zu erkennen und es NICHT als “Funktionsrumpf” zu deuten!

Zwei Parameter und zwei Rückgabewerte

1: 
let swap (x,y) = (y,x)      // Die Werte im Tupel werden vertauscht

Weil der Rückgabewert hier ein Tupel ist, können zwei Werte zurückgegeben werden.

Drei Parameter und drei Rückgabewerte

1: 
let shift (x,y,z) = (y,z,x)     // Die Werte im Tupel werden nach links geschoben

Kein Parameter und kein Rückgabewert

Jetzt wird es interessant und wird doch vertraut vorkommen.

1: 
let nop () = ()     // keine Operation

Tupel Zusammenfassung

Ein leeres Tupel wird so () geschrieben.
Man nennt es Unit oder seltener auch 0-Tupel und damit ist Unit ganz logisch.

Ein 1-Tupel wäre dann z.B. ("X")
was ausgewertet val it : string = "X"
entspricht, also wie "X".
D.h. bei 1-Tupel kann man auch die Klammer weglassen.

1: 
2: 
3: 
let sqr (x) = (x * x)       // val sqr : x:int -> int
let sqr (x) =  x * x        // val sqr : x:int -> int
let sqr  x  =  x * x        // val sqr : x:int -> int

Diese Parameter sind Tupel

1: 
2: 
let add (x,y) = x + y       // val add : x:int * y:int -> int
let add(x,y)  = x + y       // val add : x:int * y:int -> int   // kein space zwischen "add" und "("

Und Achtung, diese Parameter sind KEINE Tupel

1: 
let add  x y  = x + y       // val add : x:int -> y:int -> int

Ein n-Tupel hat dann die Anzahl max 0 (n-1) von Kommas (x , y) in der Schreibweise und Sternchen (x * y) in der Signatur.

Funktions Signatur

Funktionen und deren Signatur.
Die Signatur muss man nicht selbst deklarieren, sie entsteht automatisch durch die Komposition der Parameter und des Rückgabewertes.

1: 
2: 
let add  x y  = x + y       // val add : x:int -> y:int -> int
let add (x,y) = x + y       // val add : x:int  * y:int -> int

Bedeutung der Symbole der Signatur

  • x:int : Der Doppelpunkt sagt der Wert x ist vom Type int
  • -> : Der Pfeil trennt die aneinander Reihung von Parametern und Rückgabewert (immer als Letztes)
  • * : Der Stern steht quasi als Ersatz für das Komma im Tupel. Das kommt daher, dass ein Tupel ein Produkt Type ist. Produkt im Sinne von Multiplikation. Mehr dazu in einem späteren Kapitel.

Wichtig: Der Letzte ist immer der Rückgabewert. Und es gibt IMMER einen Rückgabewert, er kann auch Unit () sein.

Hier noch ein Signatur Beispiel mit unit

1: 
let nop () = ()     // val nop : unit -> unit

Wichtig: Kommt kein -> in der Signatur vor, dann ist es keine Funktion, sondern ein Wert.

Unit kann auch im Tupel vorkommen:

1: 
let nop2 () = ((),())   // val nop2 : unit -> unit * unit

Und weil das Wesentliche eines Tupels das Komma ist, kann man die Klammerung in bestimmen Fällen weglassen, ausser bei () und links von = einer let Anweisung.

1: 
2: 
3: 
4: 
let nop2  ()     = ((),())  // val nop2 : unit -> unit * unit
let nop2  ()     =  (),()   // val nop2 : unit -> unit * unit
let nop4 ((),()) =  (),()   // val nop4 : unit * unit -> unit * unit
let nop4  (),()  =  (),()   // error FS0039: The pattern discriminator 'nop4' is not defined

Tupels sind praktisch und ohne grosses Zeremoniell definiert, man braucht nur Komma und Klammer.

Generell können beliebige Tupels in Tupeln vorkommen.

Die weiteren Vorteile zu Tupeln werden dann im Kapitel Pattern Matching behandelt.

Ausstehende Kapitel

Themen die hier angeschnitten wurden.

  • Pattern Matching mit Tupel
  • Produkt Type
val sqr : x:int -> int

Full name: FunktioneninF.sqr

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

Full name: FunktioneninF.add

val y : int
val swap : x:'a * y:'b -> 'b * 'a

Full name: FunktioneninF.swap

val x : 'a
val y : 'b
val shift : x:'a * y:'b * z:'c -> 'b * 'c * 'a

Full name: FunktioneninF.shift

val z : 'c
val nop : unit -> unit

Full name: FunktioneninF.nop

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

Full name: FunktioneninF.add

val nop2 : unit -> unit * unit

Full name: FunktioneninF.nop2

val nop4 : unit * unit -> unit * unit

Full name: FunktioneninF.nop4