Cheat Sheet

F# Cheat Sheet

F# Cheat Sheet

Cheat Sheet

F# Cheat Sheet


Variablen

In F# verwendet man in der Regel Typherleitung. Zeigen Sie mit der Maus auf einen Wert, um den hergeleiteten Typ zu sehen:

let a = 42 // int
let b = "Oliver Sturm" // string

Typen werden in F# nur selten explizit angegeben. Es ist aber natürlich möglich:

let a : int = 42;
let b : string = "Oliver Sturm";

Variablen sind in F# im Standard unveränderlich. Erst mit dem Zusatz mutable kann man sie ändern:

let a = 42 // Unveränderbarer Wert (Zahl)
// a <- a + 1 // nicht möglich
let b = "Oliver Sturm" // Unveränderbarer Wert (Zeichenkette)
let mutable c = "Holger Schwichtenberg" // Veränderbare Variable
c <- "Dr. " + c

Literale

// Zahlen
let zahl = 42
let gebrocheneZahl = 42.5
let grosseZahl = 42_000_000_000.123

// Zeichenketten
let autor1 = "Oliver\nSturm" // Zeichenkette mit Escape-Sequenz
let autor2 = """Holger 
Schwichtenberg""" // Zeichenkette mit drei Anführungszeichen und Umbruch
let pfad = @"C:\temp" // Zeichenkette mit Verbatim-String

// String-Interpolation
// Interpolierte Zeichenkette (Template String)
let ausgabe1 = $"{autor1} ist älter als {zahl}."
// Interpolierte Zeichenkette mit Umbruch
let ausgabe2 = $"Autoren:\n{autor1}\nund\n{autor2}"
// Interpolierte Zeichenkette mit drei Anführungszeichen und Umbruch
let ausgabe3 = $"""Autoren: 
{autor1}
und
{autor2}"""
// Interpolierte Zeichenkette mit drei Anführungszeichen und Ausdruck
let ausgabe4 = $"""Die Zahl {a} ist eine {if a % 2 = 0 then "gerade" else "ungerade"} Zahl."""

// Boolean
let bool = true

// Regex
let regEx = System.Text.RegularExpressions.Regex("a[0-9]")

// Datum
let datum = System.DateTime(2024, 7, 15)

// Nullable, zum Austausch mit anderen .NET-Sprachen
let mutable nullableZahl = Nullable(42) // Nullable Ganzzahl
nullableZahl <- System.Nullable() // Anderen Wert zuweisen

Bildschirmausgaben

printf "Ausgabe ohne Umbruch" 
printfn "Ausgabe mit Umbruch" 
Console.WriteLine "Ausgabe mit .NET-Klasse System.Console" 

printfn "Ganzzahl: %d" zahl // Ganzzahl: 42
printfn "Kommazahl: %f" gebrocheneZahl // Kommazahl: 42.50000
printfn "Kommazahl: %8.4f" gebrocheneZahl // Kommazahl: 42.5000
printfn "Kommazahl: %08.4f" gebrocheneZahl // Kommazahl: 042.5000
printfn "Hexadezimalzahl: %x" zahl // Hexadezimalzahl: 2a
printfn "Oktalzahl: %o" zahl // Oktalzahl: 52
printfn "Binärzahl: %B" zahl // Binärzahl: 101010
printfn "Gleitkommatyp: %e" gebrocheneZahl // Ausgabe ohne Umbruch
printfn "Zeichenkette: %s" pfad // Zeichenkette: C:\Temp
printfn "Bool: %b" bool // Bool: true
printfn "Datum: %O" datum // Datum: 15.07.2024 00:00:00
printfn "RegEx: %O" regEx // RegEx:a[0-9]

printfn "Zahl oder null: %A" nullableZahl // Zahl oder null: 42
nullableZahl <- Nullable() // null zuweisen geht nicht mit null!
printfn "Zahl oder null: %A" nullableZahl // Zahl oder null: <null>
Console.WriteLine("null an Methode übergeben: {0}", null);

// Ausgabe mehrerer Werte
printfn "%s ist älter als %d" b a // Oliver Sturm ist älter als 42
// Ausgabe mit .NET-Klasse System.Console
Console.WriteLine "Ausgabetext"
Console.WriteLine (b + " ist älter als " + a.ToString() + ".") // Oliver Sturm ist älter als 42

// Ausgabe in eine Zeichenkette
let ausgabe = sprintf "%s ist älter als %d" b a
printfn "%s" ausgabe

Bedingungen

let random = Random()
let zz = random.Next(1, 11) // Generiert eine Zufallszahl zwischen 1 und 10

if zz > 5 then
  printfn "Die Zahl %d ist größer als 5" zz
elif zz = 5 then
  printfn "Die Zahl ist gleich 5"
else
  printfn "Die Zahl %d ist kleiner als 5" zz

Operatoren

printfn "%d" (1 + 2) // 3
printfn "%d" (1 - 2) // -1
printfn "%d" (1 * 2) // 2
printfn "%f" (1.0 / 2.0) // 0.5
printfn "%d" (1 % 2) // 1 (Modulus)
printfn "%d" (pown 2 3) // 8 (Exponentiation)
printfn "%b" (1 > 2) // false
printfn "%b" (1 < 2) // true
printfn "%b" (1 >= 2) // false
printfn "%b" (1 <= 2) // true
printfn "%b" (1 = 2) // false (gleich)
printfn "%b" (1 <> 2) // true (ungleich)
printfn "%d" (if 1 <> 0 then 42 else 43) // 42 (ternärer Operator)
printfn "%d" (if 1=1 && 2=2 then 42 else 43) // 42 (logisches Und)
printfn "%d" (if 1=2 || 2=2 then 42 else 43) // 42 (logisches Oder)
printfn "%b" (not (1 <> 0)) // false (logisches Nicht)
printfn "%d" (1 &&& 2) // 0 (bitweises Und)
printfn "%d" (1 ||| 2) // 3 (bitweises Oder)
printfn "%d" (1 ^^^ 2) // 3 (bitweises Exklusiv-Oder)
printfn "%d" (~~~1) // -2 (bitweises Nicht)
printfn "%d" (1 <<< 2) // 4 (bitweises Linksverschieben)
printfn "%d" (1 >>> 2) // 0 (bitweises Rechtsverschieben mit Vorzeichen)

printfn "\nZeichenketten-Operatoren\n%s\n\n" (String.replicate 24 "-")

let a = "Holger Schwichtenberg";
let b = "Dr. " + a // Verkettung
printfn "%s" b // Dr Holger Schwichtenberg

let c = b[0] // Nur das erste Zeichen via Index
printfn "%c" c // D

let d = b[4..9] // Zeichen 4 bis 9 via Slicing
printfn "%s" d // Holger

Ternäre Ausdrücke

let random = Random()
let z1 = random.Next(1, 11)

printfn $"""Die Zufallszahl %d{z1} ist {(if z1 > 5 then "größer als" elif z1 < 5 then "kleiner als" else "gleich")} 5"""

Schleifen

printfn "for...do-Schleife vorwärts"
for i in 1 .. 10 do
  printf "%d " i

printfn "\n\nfor...do-Schleife rückwärts"
for i in 10 .. -1 .. 1 do
  printf "%d " i

printfn "\n\nfor..in-Schleife über die Elemente eines Arrays"
let array = [| 1; 2; 3 |]
for x in array do
  printf "%d " x
  
printfn "\n\nfor..in-Schleife über ein Dictionary"
let objektMitWerten = dict ["a", 1; "b", 2; "c", 3]
for KeyValue(k, v) in objektMitWerten do
  printfn "Schlüssel %s = Wert %d" k v
  
printfn "\nwhile-Schleife (kein do...while, kein break)"
let mutable i = 1
while i <= 10 do
  printf "%d " i
  i <- i + 1

Rekursive Alternativen zu Schleifen

Dies ist der traditionelle und typische Ansatz für Wiederholungen in funktionalen Sprachen. Der F#-Compiler kann die Rekursion optimieren, sodass sie nicht zu einem Stack-Overflow führt:

// Rekursive Alternative zur while-Schleife mit Abbruchbedingung
let rec loop i =
  // Willkürliche Abbruchbedingung, ähnlich wie break
  if i = 5 then ()

  elif i <= 10 then
    printf "%d " i
    loop (i + 1)
loop 1

printfn "\n\nRekursive Schleife - äquivalent zu do...while"
let rec loopDoWhile i =
  printf "%d " i
  if i <= 10 then // absichtlich <=, um die Äquivalenz zu do...while zu demonstrieren
    loopDoWhile (i + 1)
loopDoWhile 1

Funktionen

Funktion ohne Parameter:

let output = 
  printfn "Hallo, Welt!"

output // Hallo, Welt!

Funktion mit einem Parameter:

let greet name = 
  printfn "Hallo, %s!" name

greet "Holger" // Hallo, Holger!

Funktion mit mehreren Parametern:

let info name alter wohnort = // Funktion mit 3 Parametern
  printfn "%s ist %d Jahre alt und wohnt in %s." name alter wohnort

info "Holger" 51 "Essen" // Holger ist 51 Jahre alt und wohnt in Essen.

Funktion mit einem Parameter und Typangabe:

let greet (name:string) = 
  printfn "Hallo, %s!" name
  
greet "Holger" // Hallo, Holger!

Funktion mit einem (Tupel-)Parameter und Typangabe:

Viele C#-Programmierer nehmen hier fälschlich an, dass diese Funktion mehrere Parameter hat. Prüfen Sie mit der Maus die hergeleitete Signatur! Diese Syntax wird generell nur für Interop mit .NET verwendet.

let info (name:string, alter:int) = // Funktion mit Tupel als Parameter
  printfn "%s ist %d Jahre alt." name alter

info ("Holger", 51) // Holger ist 51 Jahre alt.

Funktion mit mehreren Parametern und Typangabe:

let info (name:string) (alter:int) = // Funktion mit 2 Parametern
  printfn "%s ist %d Jahre alt." name alter

info "Holger" 51 // Holger ist 51 Jahre alt.

Funktion mit Rückgabewert:

let infoString name alter = 
  let r = sprintf "%s ist %d Jahre alt" name alter
  r + "." // letzter Ausdruck ist der Rückgabewert

let text = infoString "Holger" 51
printfn "%s" text // Holger ist 51 Jahre alt.

// Oder kürzer:
let infoString2 name alter = sprintf "%s ist %d Jahre alt." name alter

printfn "%s" (infoString2 "Holger" 51) // Holger ist 51 Jahre alt.

Klammern beim Funktionsaufruf:

Funktionsaufrufe in F# verwenden keine Klammer, Parameter werden durch Leerzeichen getrennt. Stattdessen muss geklammert werden, wenn das Resultat eines Ausdrucks als Parameter an eine Funktion übergeben wird.

let square x = x * x

let square15 = square 15
let square10plus5 = square (10 + 5)

let add x y = x + y

add (10 + 3) (30 - 1) // Zum Vergleich in C#: add(10 + 3, 30 - 1)

// Bei Bedarf Klammern schachteln
printfn "%d" (add (10 + 3) (30 - 1)) // 42

Hergeleitete Typen werden durch Verwendung beeinflusst:

// Oben hat diese Funktion die Signatur int -> int -> int
// Diesmal allerdings float -> float -> float
let add x y = x + y

// Durch diese Verwendung werden die Typen der Parameter
// als float festgelegt. 
printfn "%e" (add 12.4 13.6) // 2.600000e+001

Rekursive Funktion:

let rec factorial x = 
  match x with 
  | 0 -> 1
  | _ -> factorial (x - 1) * x

printfn "%d" (factorial 5) // 120

Gegenseitig rekursive Funktionen:

Funktionen, die sich gegenseitig aufrufen, müssen gemeinsam mit den Schlüsselworten rec und and deklariert werden.

let rec ping c v =
  match c with
  | 0 -> v
  | _ -> pong (c - 1) (v + c)

and pong c v =
  match c with
  | 0 -> v
  | _ -> ping (c - 1) (v + c)

printfn $"Ping-Pong-Ergebnis: %d{ping 20 0}" // Ping-Pong-Ergebnis: 210

Pattern-Matching-Funktion:

Solche Funktionen werden oft verwendet, wenn „nur“ ein Match ausgeführt werden soll.

let rec factorial = function
  | 0 -> 1
  | x -> factorial (x - 1) * x

printfn "%d" (factorial 5) // 120

Funktionen als Parameter:

let rec map f = function
  | [] -> []
  | x :: xs -> f x :: (map f xs)

let square x = x * x
let values = [10; 20; 30]
printfn $"Quadratzahlen: %A{map square values}" // Quadratzahlen: [100; 400; 900]

// Ad-hoc mit Lambdaausdruck
printfn $"Doppelte Werte: %A{map (fun x -> x * 2) values}" // Doppelte Werte: [20; 40; 60]

Partial Application:

// Allgemeine Funktion addiert zwei beliebige Zahlen
let add x y = x + y
// Spezielle Variante addiert immer 5
let add5 = add 5

printfn "%d" (add 22 20) // 42
printfn "%d" (add5 37) // 42

let square x = x * x
let values = [10; 20; 30]

// Allgemeine map-Funktion ruft Funktion für jedes Element einer Liste auf
printfn "%A" (List.map square values) // [100; 400; 900]

// Besondere map-Funktion addiert 5 zu jedem Listenelement
let add5List = List.map add5

printfn "%A" (add5List values) // [15; 25; 35]

// printfn mit mehreren Platzhaltern kann auch "teilweise angewendet" werden
let printTwoPart = printfn "%s: %A" 
let printResult = printTwoPart "Ergebnis"
printResult (List.map square values) // Ergebnis: [100; 400; 900]

Pipelines und Komposition:

let add5 x = x + 5
let square x = x * x

// Aufrufe können geschachtelt werden
printfn "%d" (square (add5 17)) // 484
// Pipelines ermöglichen eine natürliche Reihenfolge der Funktionsaufrufe
printfn "%d" (add5 17 |> square) //...