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
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.
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})";
});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);fn add(a: number): (number) => number {
return (b: number) => a + b;
}
let add5 = add(5);
print(add5(3)); // → 8Praktische 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