EMZETT.
Login

Kurz: Records sind unveränderliche Datensätze mit benannten Feldern und eingebauter Gleichheit:

Teil des Kurses F#

Kapitel 4 von 8 im Kurs F#. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Records

Records sind unveränderliche Datensätze mit benannten Feldern und eingebauter Gleichheit:

type Person = { Name: string; Alter: int }
 
let p = { Name = "Mia"; Alter = 17 }
let p2 = { p with Alter = 18 }
printfn "%A" p
printfn "%A" p2
printfn "%b" (p = { Name = "Mia"; Alter = 17 })
printfn "%s ist %d" p.Name p.Alter
 
let { Name = n; Alter = a } = p2
printfn "%s %d" n a
 
type Punkt = { X: float; Y: float }
let abstand (a: Punkt) (b: Punkt) = sqrt ((a.X - b.X) ** 2.0 + (a.Y - b.Y) ** 2.0)
printfn "%.1f" (abstand { X = 0.0; Y = 0.0 } { X = 3.0; Y = 4.0 })
 
type Konto =
    { Inhaber: string
      mutable Stand: decimal }
    member this.Einzahlen(betrag) = this.Stand <- this.Stand + betrag
    override this.ToString() = sprintf "%s: %M" this.Inhaber this.Stand
 
let k = { Inhaber = "Mia"; Stand = 100m }
k.Einzahlen 25.5m
printfn "%O" k

Ausgabe:

{ Name = "Mia"
  Alter = 17 }
{ Name = "Mia"
  Alter = 18 }
true
Mia ist 17
Mia 18
5.0
Mia: 125.5

Discriminated Unions

Unions beschreiben „entweder-oder“-Daten. Der Compiler prüft, dass match alle Fälle abdeckt:

type Form =
    | Kreis of radius: float
    | Rechteck of breite: float * hoehe: float
    | Dreieck of float * float * float
 
let flaeche form =
    match form with
    | Kreis r -> System.Math.PI * r * r
    | Rechteck(b, h) -> b * h
    | Dreieck(a, b, c) ->
        let s = (a + b + c) / 2.0
        sqrt (s * (s - a) * (s - b) * (s - c))
 
for f in [ Kreis 1.0; Rechteck(2.0, 3.0); Dreieck(3.0, 4.0, 5.0) ] do
    printfn "%A -> %.2f" f (flaeche f)
 
type Farbe = Rot | Gruen | Blau
let name = function Rot -> "rot" | Gruen -> "grün" | Blau -> "blau"
printfn "%s" (name Gruen)
 
type Ausdruck =
    | Zahl of int
    | Plus of Ausdruck * Ausdruck
    | Mal of Ausdruck * Ausdruck
    | Neg of Ausdruck
 
let rec auswerten = function
    | Zahl n -> n
    | Plus(a, b) -> auswerten a + auswerten b
    | Mal(a, b) -> auswerten a * auswerten b
    | Neg a -> -(auswerten a)
 
let e = Mal(Plus(Zahl 1, Zahl 2), Neg(Zahl 4))
printfn "%A = %d" e (auswerten e)
 
type Baum<'a> =
    | Blatt
    | Knoten of Baum<'a> * 'a * Baum<'a>
 
let rec einfuegen x = function
    | Blatt -> Knoten(Blatt, x, Blatt)
    | Knoten(l, w, r) as k ->
        if x < w then Knoten(einfuegen x l, w, r)
        elif x > w then Knoten(l, w, einfuegen x r)
        else k
 
let rec inOrdnung = function
    | Blatt -> []
    | Knoten(l, w, r) -> inOrdnung l @ [ w ] @ inOrdnung r
 
let baum = List.fold (fun b x -> einfuegen x b) Blatt [ 5; 3; 8; 1; 4; 7; 9; 3 ]
printfn "%A" (inOrdnung baum)

Ausgabe:

Kreis 1.0 -> 3.14
Rechteck (2.0, 3.0) -> 6.00
Dreieck (3.0, 4.0, 5.0) -> 6.00
grün
Mal (Plus (Zahl 1, Zahl 2), Neg (Zahl 4)) = -12
[1; 3; 4; 5; 7; 8; 9]

Option und Result: Fehler ohne Ausnahmen

let teile a b = if b = 0 then None else Some(a / b)
 
printfn "%A %A" (teile 10 2) (teile 1 0)
printfn "%d %d" (teile 8 2 |> Option.defaultValue -1) (teile 8 0 |> Option.defaultValue -1)
printfn "%A" (teile 8 2 |> Option.map (fun x -> x + 1))
printfn "%A" (teile 8 2 |> Option.bind (fun x -> teile 100 x))
 
match teile 7 0 with
| Some v -> printfn "Ergebnis %d" v
| None -> printfn "Division durch null"
 
type Fehler = DurchNull | Negativ of int
 
let wurzel n = if n < 0 then Error(Negativ n) else Ok(int (sqrt (float n)))
let sicherTeile a b = if b = 0 then Error DurchNull else Ok(a / b)
 
let berechne a b =
    match sicherTeile a b with
    | Error e -> Error e
    | Ok q -> wurzel q
 
printfn "%A" (berechne 100 4)
printfn "%A" (berechne 1 0)
printfn "%A" (berechne -16 2)
 
let ergebnis = sicherTeile 100 5 |> Result.bind wurzel |> Result.map (fun x -> x * 10)
printfn "%A" ergebnis
printfn "%A" (Option.ofObj (null: string))
printfn "%A" (Some 5 |> Option.filter (fun x -> x > 10))

Ausgabe:

Some 5 None
4 -1
Some 5
Some 25
Division durch null
Ok 5
Error DurchNull
Error (Negativ -8)
Ok 40
None
None

Aktive Muster und Einheiten

let (|Gerade|Ungerade|) n = if n % 2 = 0 then Gerade else Ungerade
 
let beschreibe = function
    | Gerade -> "gerade"
    | Ungerade -> "ungerade"
 
printfn "%s %s" (beschreibe 4) (beschreibe 7)
 
let (|Zahl|_|) (s: string) =
    match System.Int32.TryParse s with
    | true, n -> Some n
    | _ -> None
 
for s in [ "42"; "abc"; "7" ] do
    match s with
    | Zahl n -> printfn "%s ist die Zahl %d" s n
    | _ -> printfn "%s ist keine Zahl" s
 
[<Measure>] type m
[<Measure>] type s
let strecke = 100.0<m>
let zeit = 9.58<s>
printfn "%.2f" (float (strecke / zeit))

Ausgabe:

gerade ungerade
42 ist die Zahl 42
abc ist keine Zahl
7 ist die Zahl 7
10.44

Merke

  • Records ({ Name = ...}) sind unveränderlich; { r with ... } kopiert mit Änderung
  • Discriminated Unions beschreiben Alternativen; match wird vollständig geprüft
  • option und Result ersetzen null und Ausnahmen bei erwartbaren Fehlern
  • Aktive Muster und Einheiten (<m>) erweitern die Ausdruckskraft

Übungsaufgabe

Modelliere einen Ausdrucksbaum mit Division und gib bei Division durch null ein Error zurück.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Listen, Arrays, Maps und Sequenzen

Weiter: Klassen, Interfaces und Module

Alle Kapitel: F# im Überblick