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
// 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
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
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
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
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"""
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
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
let output =
printfn "Hallo, Welt!"
output // Hallo, Welt!
let greet name =
printfn "Hallo, %s!" name
greet "Holger" // Hallo, Holger!
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.
let greet (name:string) =
printfn "Hallo, %s!" name
greet "Holger" // Hallo, Holger!
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.
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.
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.
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
// 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
let rec factorial x =
match x with
| 0 -> 1
| _ -> factorial (x - 1) * x
printfn "%d" (factorial 5) // 120
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
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
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]
// 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]
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) //...