Tutorial 4~15 MinutenMittel

Closures & Lambdas

Verstehe anonyme Funktionen, Datenkapselung und funktionale Programmierung in Velisch.

Was du lernen wirst

  • Was Closures sind und wie sie funktionieren
  • Lambda-Syntax für kompakte Funktionen
  • Closures als Rückgabewerte (Higher-Order Functions)
  • Praktische Anwendungsfälle
1

Was sind Closures?

Eine Closure ist eine Funktion, die Variablen aus ihrem umgebenden Scope "einfängt" und sich diese merkt – auch nachdem der ursprüngliche Scope nicht mehr existiert. Das macht sie extrem mächtig für Callbacks, Event-Handler und funktionale Programmierung.

Das Prinzip verstehen

// Normale Funktion – kennt nur ihre Parameter
fn add(a: number, b: number): number {
    return a + b;
}

// Closure – "fängt" externe Variable ein
let factor = 10;
let multiply = (n: number) => n * factor;  // factor wird eingefangen

print(multiply(5));   // → 50
print(multiply(3));   // → 30

// factor ist "gefangen" – auch wenn wir den Scope verlassen
fn createMultiplier(): (number) => number {
    let localFactor = 10;
    return (n: number) => n * localFactor;  // localFactor bleibt erhalten
}

let mult = createMultiplier();
print(mult(5));  // → 50  (localFactor "lebt" weiter in der Closure)

🔒 Was wird eingefangen?

Closures fangen nur die Variablen ein, die sie tatsächlich verwenden. Der Compiler ist smart genug, nur das Nötigste zu speichern. Das nennt man Capture by Need.

2

Lambda-Syntax

Lambdas sind anonyme Funktionen – Funktionen ohne Namen. Velisch bietet eine kompakte Pfeil-Syntax => für deren Definition.

Verschiedene Schreibweisen

// Kurzform: Ein Parameter, ein Ausdruck
let double = n => n * 2;

// Mit Typ-Annotation
let double = (n: number) => n * 2;

// Mehrere Parameter
let add = (a: number, b: number) => a + b;

// Mit Block-Body (mehrere Statements)
let process = (n: number) => {
    let doubled = n * 2;
    let result = doubled + 10;
    return result;
};

// Ohne Parameter
let getTimestamp = () => now();

// Mit explizitem Rückgabetyp
let divide = (a: number, b: number): number => a / b;

Lambdas in Collection-Methoden

Der häufigste Anwendungsfall – kompakte Transformationen:

let users = [
    User { name: "Anna", age: 25 },
    User { name: "Max", age: 30 },
    User { name: "Lisa", age: 22 },
];

// Kurzform für einfache Operationen
let names = users.map(u => u.name);
let adults = users.filter(u => u.age >= 18);
let totalAge = users.reduce((sum, u) => sum + u.age, 0);

// Komplexere Logik mit Block-Body
let descriptions = users.map(u => {
    let status = if (u.age >= 30) "Senior" else "Junior";
    return "{u.name} ({status})";
});
3

Higher-Order Functions

Eine Higher-Order Function ist eine Funktion, die entweder eine Funktion als Parameter nimmt oder eine Funktion zurückgibt. Closures machen das erst richtig nützlich.

Funktionen als Rückgabewert

// Factory-Funktion: Erzeugt spezialisierte Addierer
fn createAdder(x: number): (number) => number {
    return (y: number) => x + y;  // x wird eingefangen
}

let addFive = createAdder(5);
let addTen = createAdder(10);

print(addFive(3));   // → 8
print(addTen(3));    // → 13

// Praktisches Beispiel: Formatter erstellen
fn createFormatter(prefix: string, suffix: string): (string) => string {
    return (text: string) => "{prefix}{text}{suffix}";
}

let wrapInBrackets = createFormatter("[", "]");
let wrapInQuotes = createFormatter('"', '"');

print(wrapInBrackets("Hallo"));  // → "[Hallo]"
print(wrapInQuotes("Test"));     // → '"Test"'

Funktionen als Parameter

// Eigene Higher-Order Function
fn applyTwice(f: (number) => number, x: number): number {
    return f(f(x));
}

let double = (n: number) => n * 2;
print(applyTwice(double, 5));  // → 20 (5 → 10 → 20)

// Retry-Logik mit Callback
fn retry<T>(action: () => T, maxAttempts: number): T {
    let mut attempts = 0;
    while (attempts < maxAttempts) {
        try {
            return action();
        } catch (e) {
            attempts = attempts + 1;
            if (attempts >= maxAttempts) {
                throw e;
            }
        }
    }
}

let result = retry(() => fetchData(), 3);
4

Praktische Anwendungen

Closures sind überall in modernem Code. Hier sind die wichtigsten Patterns:

1. Event-Handler & Callbacks

// Button-Click mit Zustand
let mut clickCount = 0;

button.onClick(() => {
    clickCount = clickCount + 1;
    print("Geklickt: {clickCount} mal");
});

// Async-Operation mit Callback
fetchUser(userId, (user) => {
    print("User geladen: {user.name}");
});

2. Datenkapselung (Private State)

// Counter mit privatem State
fn createCounter(): { increment: () => void, get: () => number } {
    let mut count = 0;  // Private Variable!
    
    return {
        increment: () => { count = count + 1; },
        get: () => count,
    };
}

let counter = createCounter();
counter.increment();
counter.increment();
print(counter.get());  // → 2

// count ist von außen nicht zugänglich!

3. Middleware & Wrapper

// Logging-Wrapper für Funktionen
fn withLogging<T>(name: string, action: () => T): T {
    print("[{now()}] Start: {name}");
    let result = action();
    print("[{now()}] Ende: {name}");
    return result;
}

let result = withLogging("Daten laden", () => {
    return db.findAll(User);
});

// Timing-Wrapper
fn withTiming<T>(action: () => T): T {
    let start = now();
    let result = action();
    let duration = now() - start;
    print("Dauer: {duration}ms");
    return result;
}

Performance-Hinweis

Der Velisch-Compiler optimiert Closures durch Escape Analysis. Wenn eine Closure den lokalen Scope nicht verlässt, wird sie auf dem Stack statt auf dem Heap alloziert – das ist deutlich schneller.

Zusammenfassung

Was du gelernt hast:

  • Closures fangen Variablen aus ihrem Scope ein
  • Lambda-Syntax mit =>
  • Higher-Order Functions für flexible APIs
  • Patterns: Callbacks, Kapselung, Middleware

Nächste Schritte:

  • Pattern Matching für elegante Fallunterscheidungen
  • Standard Library für eingebaute Funktionen