# Funktional programmieren mit C#

> **Die Abschnitte „In meiner Übung" zeigen echten Code** aus meinen C#-Übungen in Visual Studio: einer kleinen Rechenübung vom 18. September 2026 und meinem Übungsprojekt `learning`, an dem ich von Ende September bis zum Test am 23. Oktober 2026 gearbeitet habe. Für diese Seite habe ich beide Hauptprogramme mit .NET 10 neu kompiliert und ausgeführt, die gezeigten Ausgaben sind echt. Die Stellen mit „Zum Ausprobieren" sind kurze Beispiele, die meinen Code aufrufen oder einen Gedanken zeigen. Sie sind ebenfalls ausgeführt.

## Inhaltsverzeichnis

- [Worum es im Modul geht](#worum-es-im-modul-geht)
- [Imperativ und funktional](#imperativ-und-funktional)
- [Pure Functions und Seiteneffekte](#pure-functions-und-seiteneffekte)
- [Unveränderliche Daten mit record](#unveränderliche-daten-mit-record)
- [Ausdrücke statt Zuweisungen](#ausdrücke-statt-zuweisungen)
- [Lambdas und Funktionen als Werte](#lambdas-und-funktionen-als-werte)
- [LINQ: die Werkzeuge von Folie 116](#linq-die-werkzeuge-von-folie-116)
- [Query- und Lambda-Schreibweise](#query--und-lambda-schreibweise)
- [Von der Schleife zur LINQ-Kette](#von-der-schleife-zur-linq-kette)
- [Rekursion und Endrekursion](#rekursion-und-endrekursion)
- [Ein mathematisches Problem deklarativ beschreiben](#ein-mathematisches-problem-deklarativ-beschreiben)
- [Dieselbe Idee in fünf Sprachen](#dieselbe-idee-in-fünf-sprachen)
- [Vorteile und Nachteile](#vorteile-und-nachteile)
- [Fehler, die mir beim Üben passiert sind](#fehler-die-mir-beim-üben-passiert-sind)
- [Wo das Modul im Projekt wieder auftaucht](#wo-das-modul-im-projekt-wieder-auftaucht)
- [Zusammenfassung / Spickzettel](#zusammenfassung--spickzettel)

---

## Worum es im Modul geht

Modul 323 heisst „Funktional programmieren". Laut Modulbaukasten soll man danach Algorithmen und Teile einer Anwendung **deklarativ beschreiben** und **funktional umsetzen** können. Die vier Handlungsziele, in meinen Worten:

1. Anforderungen so beschreiben, dass man sie funktional umsetzen kann
2. Algorithmen und Teilprobleme mit Funktionen lösen
3. Bestehenden imperativen Code funktional verbessern (Refactoring)
4. Die Lösung auf Korrektheit und Qualität prüfen

Im Unterricht lief das in C#. Java, JavaScript, Python und PHP kamen dazu, weil man dieselbe Idee auch dort lesen und umschreiben können soll.

## Imperativ und funktional

**Imperativ** heisst: Ich sage dem Computer Schritt für Schritt, **wie** er etwas tun soll. Variablen werden angelegt und immer wieder überschrieben, Schleifen zählen hoch. **Funktional** (deklarativ) heisst: Ich beschreibe, **was** herauskommen soll, und baue das Ergebnis aus Funktionen zusammen, die nichts verändern.

| | Imperativ | Funktional |
|---|---|---|
| Frage | Wie soll es gemacht werden? | Was soll herauskommen? |
| Typisch | Schleifen, Variablen, die sich ändern | Funktionen, Ausdrücke, unveränderliche Werte |
| Zwischenstand | steht in einer Variable, die sich ändert | wird von Funktion zu Funktion weitergereicht |

Das gleiche Problem lässt sich auch objektorientiert lösen. Wie das aussieht, steht auf meiner Seite zu [M320](m320.html). Die drei Stile schliessen sich nicht aus: C# ist objektorientiert und hat seit Jahren immer mehr funktionale Werkzeuge bekommen.

### In meiner Übung: Summe der Quadrate mit einer Schleife

Das Beispiel aus dem Skript (Folie 108) ist die Summe der Quadrate einer Liste. Meine erste Fassung war klassisch imperativ: eine Variable `summe`, die in jedem Durchlauf grösser wird.

```csharp
static int SummeDerQuadrate(int[] zahlen)
{
    int summe = 0;
    foreach (int z in zahlen)
    {
        summe += z * z;
    }
    return summe;
}
```

Mit `{ 1, 2, 3, 4, 5 }` kommt `55` heraus.

### Zum Ausprobieren: dieselbe Rechnung als Ausdruck

```csharp
Console.WriteLine(SummeDerQuadrate(new int[] { 1, 2, 3, 4, 5 }));

static int SummeDerQuadrate(int[] zahlen) => zahlen.Select(z => z * z).Sum();
```

```text
55
```

Gleiches Ergebnis, aber ohne Variable, die sich ändert. `Select` macht aus jeder Zahl ihr Quadrat (Transformation), `Sum` fasst alles zu einem Wert zusammen (Akkumulation). Das sind die zwei Grundbewegungen, die in diesem Modul immer wieder vorkommen.

## Pure Functions und Seiteneffekte

Eine **Pure Function** hat zwei Eigenschaften:

1. **Gleiche Eingabe, gleiches Ergebnis.** Das Ergebnis hängt nur von den Argumenten ab.
2. **Keine Seiteneffekte.** Sie verändert nichts ausserhalb von sich: keine Variable draussen, keine Datei, keine Ausgabe in der Konsole.

Daraus folgt die **referenzielle Transparenz**: Ein Aufruf darf überall durch sein Ergebnis ersetzt werden, ohne dass sich das Programm anders verhält. `Quadrat(5)` kann man überall durch `25` ersetzen. Nicht pure sind zum Beispiel `DateTime.Now` (die Uhrzeit ändert sich), Zufallszahlen und alles, was `Console.WriteLine` aufruft.

### Zum Ausprobieren: pure gegen nicht pure

```csharp
int zaehler = 0;

Console.WriteLine($"{Quadrat(5)} {Quadrat(5)}");
Console.WriteLine($"{Zaehle()} {Zaehle()}");

int Quadrat(int x) => x * x;
int Zaehle() => ++zaehler;
```

```text
25 25
1 2
```

`Quadrat` gibt zweimal dasselbe. `Zaehle` gibt bei genau gleichem Aufruf zuerst `1`, dann `2`, weil es die Variable `zaehler` ausserhalb von sich verändert. Wer `Zaehle()` durch `1` ersetzt, ändert das Programm. Genau das macht pure Funktionen so leicht zu testen: Eingabe rein, Ergebnis prüfen, fertig. Es gibt keinen Zustand, den man vorher herstellen muss.

## Unveränderliche Daten mit record

**Immutability** heisst: Ein Wert ändert sich nach dem Erzeugen nicht mehr. Wer ihn „ändern" will, bekommt einen neuen Wert, der alte bleibt, wie er war. In C# geht das am kürzesten mit einem `record`. Er beschreibt einen Datentyp in einer Zeile, und seine Felder lassen sich nach dem Erzeugen nicht mehr überschreiben.

### In meiner Übung: Person und Produkt

```csharp
record Person(string Name, int Alter);
record Produkt(string Name, double Preis);
```

Mit `with` entsteht eine Kopie, in der ein Feld anders ist:

```csharp
var mia = new Person("Mia", 17);
var mia18 = mia with { Alter = 18 };
Console.WriteLine(mia.Alter);
Console.WriteLine(mia18.Alter);
```

```text
17
18
```

`mia` bleibt 17. Es gibt jetzt zwei Personen, keine veränderte.

Ein `record` vergleicht ausserdem nach **Inhalt**. Zwei Produkte mit gleichem Namen und Preis sind gleich, auch wenn es zwei verschiedene Objekte sind:

```csharp
var produkt1 = new Produkt("Kaffee", 3.50);
var produkt2 = new Produkt("Kaffee", 3.50);
Console.WriteLine(produkt1 == produkt2 ? "Its the same" : "its not the same");
var produkt3 = produkt1 with { Preis = 4.00 };
Console.WriteLine(produkt1 == produkt3 ? "Its the same" : "its not the same");
```

```text
Its the same
its not the same
```

Bei einer normalen Klasse wäre schon der erste Vergleich falsch, dort prüft `==` nur, ob beide Variablen auf **dasselbe** Objekt zeigen.

### Zum Ausprobieren: Referenz gegen Wert

Warum das wichtig ist, sieht man, wenn zwei Variablen auf dasselbe veränderbare Objekt zeigen:

```csharp
var a = new Konto { Stand = 100 };
var b = a;
b.Stand = 0;
Console.WriteLine(a.Stand);

var c = new KontoR(100);
var d = c with { Stand = 0 };
Console.WriteLine($"{c.Stand} {d.Stand}");

class Konto { public int Stand; }
record KontoR(int Stand);
```

```text
0
100 0
```

Über `b` wurde auch `a` geleert, obwohl im Code nirgends `a.Stand = 0` steht. Solche versteckten Abhängigkeiten gibt es mit unveränderlichen Werten nicht. Darum eignen sie sich auch für Programme, die mehrere Dinge gleichzeitig tun: Was sich nicht ändern kann, kann kein anderer Teil des Programms unbemerkt ändern.

## Ausdrücke statt Zuweisungen

Ein **Ausdruck** wird zu einem Wert ausgewertet: `5 + 3`, `name.Length`, `x => x * 2`. Eine **Zuweisung** verändert eine Variable. Funktional schreibt man möglichst mit Ausdrücken, statt erst eine Variable anzulegen und sie dann in jedem Zweig zu überschreiben. Das Werkzeug dafür ist der Bedingungsoperator `Bedingung ? Wert wenn wahr : Wert wenn falsch`.

### In meiner Übung: Wetter und Prüfung

Das Wetter-Beispiel stammt aus dem Skript (Folie 114). Dort steht daneben eine imperative Fassung mit `string climate = string.Empty;` und einem `if`, das die Variable je nach Temperatur überschreibt. Meine Fassung ist ein einziger Ausdruck:

```csharp
static string ImmutableWeatherVerify(double tempInCelsius) =>
tempInCelsius < 20.0 ? "Cold" : "Perfect";

static string PrüfePrüfung(int punkte, string status) =>
    punkte >= 50 ? status = "Bestanden" : status = "Nicht Bestanden";
```

Bei `PrüfePrüfung` sieht man, dass ich beim ersten Versuch noch in Zuweisungen gedacht habe. Es kompiliert und liefert den richtigen Text, aber die Zuweisung an `status` braucht es nicht: Der Ausdruck **ist** schon der Wert. Der Parameter `status` kann ganz weg, übrig bleibt `punkte >= 50 ? "Bestanden" : "Nicht Bestanden"`. Mit 50 Punkten kommt „Bestanden", mit 49 „Nicht Bestanden".

## Lambdas und Funktionen als Werte

Eine **Lambda Expression** ist eine kurze Funktion ohne Namen. `x => x * x` heisst: „Nimm `x` und gib `x` mal `x` zurück." Links vom Pfeil stehen die Parameter, rechts das Ergebnis.

In C# sind Funktionen **First-Class**: Man kann sie in einer Variable speichern, an eine andere Funktion übergeben und als Ergebnis zurückgeben. Der Typ dafür ist `Func<...>`. Der **letzte** Typ in den spitzen Klammern ist immer der Rückgabetyp: `Func<int, bool>` nimmt einen `int` und gibt einen `bool` zurück.

Eine Funktion, die eine andere Funktion als Parameter nimmt oder zurückgibt, heisst **Higher-Order Function**. `Where`, `Select` und alle anderen LINQ-Methoden sind solche Funktionen.

### In meiner Übung: eine Summe mit austauschbarer Bedingung

Diese Zeilen stammen aus einer zweiten Datei, die ich kurz vor dem Test angefangen habe. Zwei Bedingungen sind als Werte gespeichert, und `SummiereMitBedingung` bekommt die Bedingung als Parameter:

```csharp
Func<int, bool> istGerade = x => x % 2 == 0;
Func<int, bool> istGroesserZehn = x => x > 10;

static int SummiereMitBedingung(int[] zahlen, Func<int, bool> f) => zahlen.Where(f).Sum();
```

### Zum Ausprobieren: dieselbe Funktion, drei Bedingungen

```csharp
Func<int, bool> istGerade = x => x % 2 == 0;
Func<int, bool> istGroesserZehn = x => x > 10;
int[] zahlen = new int[] { 3, 8, 12, 15, 6 };

Console.WriteLine(SummiereMitBedingung(zahlen, istGerade));
Console.WriteLine(SummiereMitBedingung(zahlen, istGroesserZehn));
Console.WriteLine(SummiereMitBedingung(zahlen, x => x < 5));

static int SummiereMitBedingung(int[] zahlen, Func<int, bool> f) => zahlen.Where(f).Sum();
```

```text
26
27
3
```

`SummiereMitBedingung` weiss nicht, wonach gefiltert wird. Das entscheidet, wer sie aufruft. Für eine neue Bedingung braucht es keine neue Methode, nur ein neues Lambda, wie bei `x => x < 5`.

### Zum Ausprobieren: Closure und Komposition

Eine **Closure** ist ein Lambda, das eine Variable von ausserhalb mitnimmt. **Komposition** heisst, zwei Funktionen hintereinanderzuschalten: Das Ergebnis der ersten ist die Eingabe der zweiten.

```csharp
int faktor = 3;
Func<int, int> malFaktor = x => x * faktor;
Func<int, int> plus1 = x => x + 1;
Func<int, int> kombi = x => malFaktor(plus1(x));

Console.WriteLine(malFaktor(5));
Console.WriteLine(kombi(2));
```

```text
15
9
```

`malFaktor` kennt `faktor`, obwohl es kein Parameter ist. `kombi(2)` rechnet zuerst `2 + 1 = 3`, dann `3 * 3 = 9`.

## LINQ: die Werkzeuge von Folie 116

**LINQ** (Language Integrated Query) ist der Werkzeugkasten in C#, mit dem man Daten filtert, umwandelt und zusammenfasst. Jede Methode bekommt ein Lambda, das sagt, was mit einem einzelnen Element passieren soll.

| Methode | Was sie macht | Ergebnis |
|---|---|---|
| `Where(x => ...)` | filtern | die Elemente, bei denen die Bedingung wahr ist |
| `Select(x => ...)` | umwandeln | ein neues Element pro altem |
| `OrderBy(x => ...)` | sortieren | dieselben Elemente, neu geordnet |
| `Sum`, `Count`, `Max`, `Average` | zusammenfassen | ein einzelner Wert |
| `Aggregate(start, (acc, x) => ...)` | eigene Akkumulation | ein einzelner Wert |
| `Any(x => ...)`, `All(x => ...)` | prüfen | `true` oder `false` |
| `FirstOrDefault(x => ...)` | suchen | das erste passende Element, sonst `null` |
| `GroupBy(x => ...)` | gruppieren | Gruppen mit gleichem Schlüssel |
| `Zip(andere, (a, b) => ...)` | zwei Folgen verbinden | ein Element pro Paar |

In anderen Sprachen heissen die drei wichtigsten `filter`, `map` und `reduce`. In C# sind das `Where`, `Select` und `Aggregate`.

### In meiner Übung: mein erstes Lambda

```csharp
string[] names = { "Peter", "Günther", "Manfred", "Uwe" };

var linqTest = names.Where(name => name.Contains('t'));

Console.WriteLine(string.Join(", ", linqTest));
```

```text
Peter, Günther
```

### In meiner Übung: die Beispiele von Folie 116

Für die Beispiele aus dem Skript habe ich mir einen `record` mit sechs Feldern und eine kleine Liste gebaut:

```csharp
record Item(
    int Id,
    string Name,
    bool IsActive,
    bool IsValid,
    int Amount,
    string Category
    );
```

```csharp
List<Item> list =
[
    new Item(1, "A", true, true, 10, "x"),
    new Item(2, "B", false, true, 20, "y"),
    new Item(3, "C", true, false, 30, "x")
];

var filtered = list.Where(x => x.IsActive);
Console.WriteLine(string.Join(", ", filtered.Select(x => x.Name)));
var mapped = list.Select(i => i.Name);
var sorted = list.OrderByDescending(i => i);
var total = list.Sum(i => i.Amount);
bool hasActiveItems = list.Any(i => i.IsActive);
bool allItemsValid = list.All(i => i.IsValid);
var idAnschauen = list.FirstOrDefault(x => x.Id == 9); //null
var grouped = list.GroupBy(i => i.Category);
int[] array1 = [1, 2, 3];
int[] array2 = [10, 20, 30];

var combined = array1.Zip(array2, (x, y) => x + y);
```

Ausgegeben wird davon nur die erste Zeile: `A, C`, die beiden aktiven Elemente. `total` wäre `60`, `hasActiveItems` `true`, `allItemsValid` `false` (C ist ungültig), `idAnschauen` `null`, weil es keine Id 9 gibt, und `combined` ergibt `11, 22, 33`.

### Was ich beim Nachprüfen gefunden habe: LINQ rechnet erst, wenn man fragt

Eine Zeile in diesem Block ist falsch, und das Programm läuft trotzdem ohne Fehler durch: `list.OrderByDescending(i => i)`. Sortiert wird nach dem ganzen `Item`, und C# weiss nicht, wann ein `Item` grösser ist als ein anderes. Dass nichts abstürzt, liegt an der **Deferred Execution**: Eine LINQ-Methode rechnet nicht sofort, sondern merkt sich nur, was zu tun ist. Gerechnet wird erst, wenn jemand das Ergebnis durchläuft. In meiner Übung hat das nie jemand getan.

### Zum Ausprobieren: den Fehler sichtbar machen

```csharp
var sorted = list.OrderByDescending(i => i);
Console.WriteLine("Bis hier kein Fehler");
Console.WriteLine(string.Join(", ", sorted.Select(i => i.Name)));
```

```text
Bis hier kein Fehler
Unhandled exception. System.InvalidOperationException: Failed to compare two elements in the array.
 ---> System.ArgumentException: At least one object must implement IComparable.
```

Gemeint war ein Feld, nach dem sortiert wird, zum Beispiel der Betrag:

```csharp
var sorted = list.OrderByDescending(i => i.Amount);
Console.WriteLine(string.Join(", ", sorted.Select(i => i.Name)));
```

```text
C, B, A
```

Merke: Ein LINQ-Ausdruck, der kompiliert, ist noch nicht geprüft. Erst `ToList()`, `Count()`, `Sum()` oder ein Durchlauf wie `string.Join` führt ihn wirklich aus.

## Query- und Lambda-Schreibweise

LINQ gibt es in zwei gleichwertigen Schreibweisen. Die **Query-Schreibweise** sieht aus wie SQL, die **Lambda-Schreibweise** (auch Methodenschreibweise) ist eine Kette von Methoden. Der Compiler übersetzt die erste in die zweite.

| Query | Lambda |
|---|---|
| `from p in personen` | `personen` |
| `where p.Alter >= 18` | `.Where(p => p.Alter >= 18)` |
| `orderby p.Alter` | `.OrderBy(p => p.Alter)` |
| `orderby z descending` | `.OrderByDescending(z => z)` |
| `select p.Name` | `.Select(p => p.Name)` |
| `group i by i.Category` | `.GroupBy(i => i.Category)` |

`select p`, also das Element selbst, fällt in der Lambda-Schreibweise einfach weg. Java, JavaScript und Python kennen nur die Methodenschreibweise, die Query-Schreibweise gibt es nur in C#.

### In meiner Übung: beide Richtungen

Von Query nach Lambda: Die Zahlen grösser als 3, absteigend sortiert und verdoppelt.

```csharp
var l2 = zahlen
    .Where(z => z > 3)
    .OrderByDescending(z => z)
    .Select(z => z * 2);
Console.WriteLine(string.Join(",", l2));
```

```text
16,12,10
```

Von Lambda nach Query: das Alter aller Personen, deren Name mit A beginnt.

```csharp
var q3 = from p in personen where p.Name.StartsWith("A") select p.Alter;
```

### Zum Ausprobieren: beide Formen, ein Ergebnis

```csharp
var q3 = from p in personen where p.Name.StartsWith("A") select p.Alter;
var alsLambda = personen.Where(p => p.Name.StartsWith("A")).Select(p => p.Alter);
Console.WriteLine($"{string.Join(", ", q3)} | {string.Join(", ", alsLambda)}");
```

```text
31 | 31
```

## Von der Schleife zur LINQ-Kette

Das wichtigste Handwerk im Modul: eine Schleife in eine funktionale Lösung umschreiben. Ich gehe dabei immer gleich vor:

1. Was ist die **Eingabe**, was das **Ergebnis**?
2. Welches `if` in der Schleife **filtert**? Das wird `Where`.
3. Welche Zeile **wandelt um** (`Add(z * z)`)? Das wird `Select`.
4. Welche Variable **sammelt auf** (`summe += ...`)? Das wird `Sum`, `Count`, `Max` oder `Aggregate`.
5. Wird am Schluss **sortiert**? Das wird `OrderBy`.
6. Die Hilfsvariablen und die Schleife fallen weg.

### In meiner Übung: vier Schleifen

So sah der Anfang meiner Übung aus, vier Aufgaben mit derselben Zahlenliste: die geraden Zahlen, die Quadrate, die Summe und die Summe der geraden Quadrate.

```csharp
int[] zahlen = new int[] { 3, 8, 1, 6, 5 };

List<int> geradeZahlen = new List<int>();
foreach (int z in zahlen)
{
    if (z % 2 == 0)
    {
        geradeZahlen.Add(z);
    }
}
Console.WriteLine(string.Join(", ", geradeZahlen));

List<int> quadratZahlen = new List<int>();
foreach (int z in zahlen)
{
    quadratZahlen.Add(z * z);
}
Console.WriteLine(string.Join(", ", quadratZahlen));

int summe = 0;
foreach (int z in zahlen)
{
    summe += z;
}
Console.WriteLine(summe);

int summeQuadrate = 0;
foreach (int z in zahlen)
{
    if (z % 2 == 0)
    {
        summeQuadrate += z * z;
    }
}
Console.WriteLine(summeQuadrate);
```

```text
8, 6
9, 64, 1, 36, 25
23
100
```

### Zum Ausprobieren: dieselben vier Aufgaben mit LINQ

```csharp
int[] zahlen = new int[] { 3, 8, 1, 6, 5 };

Console.WriteLine(string.Join(", ", zahlen.Where(z => z % 2 == 0)));
Console.WriteLine(string.Join(", ", zahlen.Select(z => z * z)));
Console.WriteLine(zahlen.Sum());
Console.WriteLine(zahlen.Where(z => z % 2 == 0).Select(z => z * z).Sum());
```

```text
8, 6
9, 64, 1, 36, 25
23
100
```

Gleiche Ausgabe, aus über 30 Zeilen werden vier. Weggefallen sind die Hilfslisten `geradeZahlen` und `quadratZahlen` und die Variablen `summe` und `summeQuadrate`, die in der Schleife immer wieder überschrieben wurden. Genau dieser veränderliche Zustand ist es, den die funktionale Programmierung vermeidet.

### In meiner Übung: Personen ab 18

Dasselbe mit einer Liste von Objekten. Zuerst mit Schleife:

```csharp
List<Person> personen = new List<Person>();
personen.Add(new Person("Mia", 17));
personen.Add(new Person("Ben", 25));
personen.Add(new Person("Ada", 31));

foreach (Person p in personen)
{
    if (p.Alter >= 18)
    {
        Console.WriteLine(p.Name);
    }
}
```

Dann als Kette. Das `if` wird `Where`, das Auslesen des Namens wird `Select`:

```csharp
var l1 = personen
    .Where(p => p.Alter >= 18)
    .Select(p => p.Name);
Console.WriteLine(string.Join(",", l1));
```

```text
Ben
Ada
Ben,Ada
```

Die ersten zwei Zeilen kommen aus der Schleife, die dritte aus der Kette. Ein Unterschied bleibt: Die Schleife gibt direkt aus, mitten in der Berechnung. Die Kette rechnet zuerst (pure) und gibt danach aus. Seiteneffekte gehören an den Rand des Programms.

## Rekursion und Endrekursion

Funktional gibt es keine Schleifen, denn eine Schleife braucht einen Zähler, der sich ändert. Wiederholung entsteht stattdessen durch **Rekursion**: Eine Funktion ruft sich selbst mit einem kleineren Problem auf. Jede Rekursion braucht zwei Teile:

- eine **Abbruchbedingung**, bei der die Antwort feststeht
- einen **Schritt**, der das Problem verkleinert und sich selbst aufruft

Ohne Abbruchbedingung ruft sich die Funktion ewig auf, bis der Speicher für Aufrufe (der Stack) voll ist: **Stack Overflow**.

**Endrekursiv** ist eine Funktion, wenn der rekursive Aufruf die **letzte** Operation ist. Dann muss nach dem Aufruf nichts mehr gerechnet werden, und der Zwischenstand läuft in einem zusätzlichen Parameter mit, dem **Akkumulator**. Manche Sprachen können das optimieren, C# und Java garantieren es nicht.

### In meiner Übung: imperativ im September

Den grössten gemeinsamen Teiler (ggT) habe ich am 18. September noch mit einer `while`-Schleife geschrieben, in der Programmier-Repetition kurz bevor das Modul anfing. Zwei Variablen werden in jedem Durchlauf überschrieben, eine dritte hält den alten Wert fest:

```csharp
static int BerechneGGT(int a, int b)
{
    while (b != 0)
    {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}
```

Das Programm fragt zwei Zahlen ab und rechnet daraus auch das kleinste gemeinsame Vielfache (kgV). Mit 12 und 18 gibt es `ggT: 6` und `kgV: 36` aus.

### Zum Ausprobieren: der ggT als Rekursion

Die Schleife lässt sich direkt als Definition schreiben: Der ggT von `a` und `0` ist `a`, sonst ist er der ggT von `b` und dem Rest `a % b`. Das kgV folgt daraus.

```csharp
Console.WriteLine($"ggT: {Ggt(12, 18)}");
Console.WriteLine($"kgV: {Kgv(12, 18)}");

static int Ggt(int a, int b) => b == 0 ? a : Ggt(b, a % b);
static int Kgv(int a, int b) => a / Ggt(a, b) * b;
```

```text
ggT: 6
kgV: 36
```

Keine Variable wird überschrieben, und `Ggt` ist endrekursiv: Der Aufruf `Ggt(b, a % b)` ist das Letzte, was passiert.

### In meiner Übung: Fibonacci

Die Fibonacci-Folge (Folie 110) ist `0, 1, 1, 2, 3, 5, 8, 13 ...`: Jede Zahl ist die Summe der zwei davor. Meine Fassung schreibt das genau so hin:

```csharp
static int Fib(int n)
{
    return n < 2 ? n : Fib(n - 1) + Fib(n - 2);
}
```

### Zum Ausprobieren: Fibonacci endrekursiv

```csharp
Console.WriteLine($"{Fib(10)} {FibEnd(10)}");

static int Fib(int n)
{
    return n < 2 ? n : Fib(n - 1) + Fib(n - 2);
}

static int FibEnd(int n, int a = 0, int b = 1) => n == 0 ? a : FibEnd(n - 1, b, a + b);
```

```text
55 55
```

Beide liefern dasselbe, aber nicht gleich schnell. `Fib` ist nicht endrekursiv, nach den zwei Aufrufen wird noch addiert, und dieselben Werte werden immer wieder neu berechnet. Für `Fib(30)` habe ich die Aufrufe gezählt: 2'692'537. `FibEnd(30)` braucht 31, weil die zwei letzten Zahlen in `a` und `b` mitlaufen.

## Ein mathematisches Problem deklarativ beschreiben

Im Modul muss man ein mathematisches Problem nicht nur lösen, sondern zuerst **beschreiben**, ohne Schritt-für-Schritt-Anleitung. Das Skript gibt dafür auf Folie 108 eine feste Gliederung vor:

1. **Problemstellung:** Was ist gegeben, was ist gesucht?
2. **Deklarative Beschreibung:** Welche Bausteine braucht es? Erzeugen, Filtern, Transformieren, Akkumulieren
3. **Endzustände:** Welche Zwischen- und Endwerte entstehen?
4. **Beispiel** mit konkreten Zahlen
5. Erst dann der **Code**

Am Beispiel einer meiner Übungsaufgaben, der Summe aller durch 3 teilbaren Zahlen:

| Teil | Inhalt |
|---|---|
| Problemstellung | Gegeben ist eine ganze Zahl n ab 1. Gesucht ist die Summe aller Zahlen von 1 bis n, die durch 3 teilbar sind |
| Deklarative Beschreibung | Erzeugen: die Zahlen von 1 bis n. Filtern: nur die mit Rest 0 bei Division durch 3. Akkumulieren: zusammenzählen |
| Endzustände | die gefilterte Liste und ein einzelner Wert, die Summe |
| Beispiel | n = 10: Liste 1 bis 10, gefiltert `3, 6, 9`, Summe `18` |

### In meiner Übung: Summe durch 3 und Quersumme

Der Code folgt der Beschreibung Zeile für Zeile. Jeder Baustein ist eine Methode:

```csharp
static int SummeDurch3(int n)
{
    return Enumerable.Range(1, n)
        .Where(x => x % 3 == 0)
        .Sum();
}
```

Die Quersumme habe ich als Transformation gelöst: Die Zahl wird zu Text, jedes Zeichen zu seiner Ziffer, dann wird summiert. `z - '0'` macht aus dem Zeichen `'4'` die Zahl 4, weil die Ziffern im Zeichensatz hintereinander liegen.

```csharp
static int QuerSumme(int n)
{
    return n.ToString()
        .Select(z => z - '0')
        .Sum();
}
```

### Zum Ausprobieren: die zwei Aufgaben aufrufen

```csharp
Console.WriteLine(SummeDurch3(10));
Console.WriteLine(QuerSumme(1234));
```

```text
18
10
```

`Enumerable.Range(1, n)` heisst übrigens „ab 1, n Zahlen" und nicht „von 1 bis n". Bei einem Startwert von 1 kommt beides auf dasselbe heraus, bei einem anderen nicht.

## Dieselbe Idee in fünf Sprachen

Filtern, umwandeln und zusammenfassen gibt es in jeder modernen Sprache, nur die Namen wechseln. Alle Zeilen unten habe ich mit der Liste `3, 8, 1, 6, 5` ausgeführt (C# mit .NET 10, Java 21, Node 24, Python 3.13, PHP 8.5).

**Filtern**, Ergebnis `[8, 6]`:

| Sprache | Code |
|---|---|
| C# | `zahlen.Where(x => x % 2 == 0)` |
| Java | `zahlen.stream().filter(x -> x % 2 == 0).toList()` |
| JavaScript | `zahlen.filter(x => x % 2 === 0)` |
| Python | `list(filter(lambda x: x % 2 == 0, zahlen))` |
| PHP | `array_values(array_filter($zahlen, fn($x) => $x % 2 === 0))` |

**Umwandeln**, Ergebnis `[9, 64, 1, 36, 25]`:

| Sprache | Code |
|---|---|
| C# | `zahlen.Select(x => x * x)` |
| Java | `zahlen.stream().map(x -> x * x).toList()` |
| JavaScript | `zahlen.map(x => x * x)` |
| Python | `list(map(lambda x: x * x, zahlen))` |
| PHP | `array_map(fn($x) => $x * $x, $zahlen)` |

**Zusammenfassen**, Ergebnis `23`:

| Sprache | Code |
|---|---|
| C# | `zahlen.Aggregate(0, (acc, x) => acc + x)` |
| Java | `zahlen.stream().reduce(0, (acc, x) -> acc + x)` |
| JavaScript | `zahlen.reduce((acc, x) => acc + x, 0)` |
| Python | `reduce(lambda acc, x: acc + x, zahlen, 0)` |
| PHP | `array_reduce($zahlen, fn($acc, $x) => $acc + $x, 0)` |

Der Pfeil des Lambdas heisst `=>` in C#, JavaScript und PHP, `->` in Java. Python schreibt `lambda x: ...` und erlaubt rechts nur einen einzigen Ausdruck. In Python braucht `reduce` die Zeile `from functools import reduce`. Java kann zwei Funktionen mit `andThen` verbinden: `plus1.andThen(quadrat).apply(2)` ergibt `9`.

Jede Sprache hat ihre eigene Falle. Alle fünf habe ich ausprobiert:

| Sprache | Falle |
|---|---|
| C# | LINQ rechnet erst beim Durchlaufen (siehe oben). `Sort()` verändert die Liste, `OrderBy` liefert eine neue |
| Java | Ein Stream lässt sich nur einmal benutzen. Ein Lambda darf eine Variable von aussen nur lesen, wenn sie danach nicht mehr geändert wird („effectively final") |
| JavaScript | `[10, 9, 1].sort()` ergibt `[1, 10, 9]`, weil ohne Vergleichsfunktion als Text sortiert wird. Richtig: `.sort((a, b) => a - b)`. Und `sort()` verändert das Array selbst |
| Python | `map` und `filter` liefern keine Liste, sondern einen Iterator. `print(map(...))` zeigt nur `<map object at ...>`, erst `list(...)` macht eine Liste daraus |
| PHP | `array_filter` behält die alten Schlüssel. Ohne `array_values` wird aus `[8, 6]` ein `{"1":8,"3":6}` |

## Vorteile und Nachteile

Das Skript fasst auf den Folien 106 und 107 zusammen, wofür sich funktionale Programmierung eignet und wo sie an Grenzen stösst:

| Vorteile | Nachteile |
|---|---|
| Zustandslos: Das Ergebnis hängt nur von den Eingaben ab | Daten lassen sich nicht verändern, für jede Änderung entsteht ein neuer Wert |
| Leicht zu testen und zu prüfen, weil es keinen versteckten Zustand gibt | Tiefe, nicht endrekursive Rekursion kann den Stack sprengen |
| Gut für Parallelisierung, weil sich nichts gegenseitig in die Quere kommt | Rekursion ist schwerer zu durchschauen, ein Fehler im Abbruch ist schnell gemacht |
| Kürzerer, genauerer Code | Nicht für alle Aufgaben geeignet, ein Programm muss irgendwann etwas ausgeben oder speichern |
| Lässt sich gut mit objektorientiertem Code kombinieren | |

In der Praxis heisst das für mich: nicht alles funktional, sondern die Berechnung funktional und die Seiteneffekte (Datenbank, Ausgabe, Dateien) gesammelt am Rand.

## Fehler, die mir beim Üben passiert sind

Diese Fehler sind mir beim Üben im Oktober wirklich passiert. Die Meldungen habe ich für diese Seite noch einmal mit .NET 10 erzeugt.

| Was ich geschrieben habe | Was der Compiler sagt | Richtig |
|---|---|---|
| `.Select(n * n)` | CS0103: Der Name "n" ist im aktuellen Kontext nicht vorhanden | `.Select(n => n * n)`, vor dem Pfeil steht immer der Parameter |
| Methode mit `=>` ohne `;` am Ende | CS1002: ; erwartet | Auch eine Methode in einer Zeile endet mit `;` |
| `record User(string Name, int Age)` ohne `;` | CS1513 und CS1514 | `record User(string Name, int Age);` |
| `b == 0 ? a : (b, a % b)` | CS0029: Der Typ "(int b, int)" kann nicht implizit in "int" konvertiert werden | `Ggt(b, a % b)`, der Name der Methode fehlte |
| `UpdateAge(u User, int newAge)` | CS0246: Der Typ- oder Namespacename "u" wurde nicht gefunden | `User u`, zuerst der Typ, dann der Name |
| `Record User(...)` | CS0246: Der Typ- oder Namespacename "Record" wurde nicht gefunden | `record` klein |
| `.Select(p => p.Price).Average()` ohne Treffer | kompiliert, stürzt beim Ausführen ab: `InvalidOperationException` | `.DefaultIfEmpty(0).Average()` gibt bei leerer Liste 0 |

Die letzte Zeile ist die gefährlichste, weil keine Meldung warnt. Der Code sieht richtig aus und funktioniert, solange es Treffer gibt.

## Wo das Modul im Projekt wieder auftaucht

Im IMS Projekt 1, der [Zusehendenverwaltung](../projects/zusehendenverwaltung.html), steht das Modul als Anforderung in der Spezifikation: Die Applikation muss objektorientiert und nach Clean Code gebaut sein, und „wo es geht, sollen auch Grundsätze der funktionalen Programmierung gelten" (REQ-100). Geplant ist, die Regelprüfung beim Eintragen für eine Präsentation als Pure Function zu bauen: Werte hinein, Antwort heraus, ohne Datenbank und ohne Uhr. So liefert sie in jedem Test dieselbe Antwort und lässt sich prüfen, ohne dass eine Datenbank läuft. Das Projekt ist noch in Arbeit.

## Zusammenfassung / Spickzettel

| Thema | Kernaussage |
|---|---|
| Imperativ und funktional | Wie, Schritt für Schritt, gegen was, als Ausdruck |
| Pure Function | Gleiche Eingabe, gleiches Ergebnis, keine Seiteneffekte |
| Referenzielle Transparenz | Ein Aufruf darf durch sein Ergebnis ersetzt werden |
| Immutability | Nicht ändern, sondern neu erzeugen: `record` und `with` |
| `record` | Vergleicht nach Inhalt, `==` ist wahr bei gleichen Werten |
| Ausdruck statt Zuweisung | `? :` statt `if` mit Variable, die überschrieben wird |
| Lambda | `x => x * x`, Parameter links, Ergebnis rechts |
| `Func<...>` | Typ einer Funktion, der letzte Typ ist der Rückgabetyp |
| Higher-Order Function | Nimmt eine Funktion als Parameter oder gibt eine zurück |
| Closure | Lambda, das eine Variable von aussen mitnimmt |
| Komposition | Zwei Funktionen hintereinander, `f(g(x))` |
| `Where`, `Select`, `Aggregate` | filter, map, reduce: filtern, umwandeln, zusammenfassen |
| Deferred Execution | LINQ rechnet erst, wenn das Ergebnis durchlaufen wird |
| Query und Lambda | `from ... where ... select` ist dasselbe wie `.Where(...).Select(...)` |
| Schleife umschreiben | `if` wird `Where`, `Add(f(x))` wird `Select`, `+=` wird `Sum` oder `Aggregate` |
| Rekursion | Abbruchbedingung und Schritt, sonst Stack Overflow |
| Endrekursion | Der Aufruf ist die letzte Operation, der Zwischenstand läuft im Akkumulator |
| Deklarative Beschreibung | Problemstellung, Beschreibung, Endzustände, Beispiel, dann Code |
