JDL – Generators und Collectors¶
Status: Draft
Geltungsbereich: Sprachebene / Stdlib-Oberfläche
Namespace: jdl::gen (Stdlib)
Warum Dieses Dokument Existiert¶
Ohne einheitliches Sequenzmodell driften APIs schnell auseinander (Arrays, Maps, Streams, Cursor jeweils mit eigener Logik).
Generators/Collectors lösen das in JDL über zwei kleine, composable Kernprotokolle.
0. Priorität¶
Ergänzt:
00-refinements.md01-grundlagen.md02-typsystem.md03-runtime.md
Bei Widerspruch gelten 00 bis 03.
1. Leitidee¶
- lazy by default,
- Pipeline-kompatibel (
|>), - typsicher erweiterbar über
provide, - Effekt- und Fehlerlogik bleibt explizit.
2. Kernabstraktionen¶
2.1 Generator[T]¶
Bedeutung:
- liefert sukzessive
Some(value)oderNone, - endlich oder unendlich möglich,
- Fehlermodell über Elementtyp (z. B.
Result[T, E]).
2.1.1 Konstruktible Referenz-Implementierung: PullGen[T]¶
Generator[T] ist ein Protocol (also ein Vertrag), kein instanziierbarer Typ.
Damit Library-Code Generatoren bauen kann, liefert die Stdlib einen kleinen
konkreten Referenztyp: PullGen[T].
type PullGen[T]: struct {
pull: () -> Option[T]
}
provide[T] Generator[T] for PullGen[T] {
def next(self) -> Option[T] = self.pull()
}
2.1.2 Builder: fromPull und unfold¶
provide PullGen {
def fromPull[T](pull: () -> Option[T]) -> PullGen[T] =
PullGen { pull: pull }
// State-Machine ohne Coroutines: `step` liefert (value, nextState) oder None.
def unfold[S, T](state: S, step: S -> Option[(T, S)]) -> PullGen[T] {
var s = state
PullGen.fromPull(() => {
match step(s) {
| Some((value, next)) => { s = next; Some(value) }
| None => None
}
})
}
}
Normativ: PullGen ist der kleinste gemeinsame Nenner für "lazy Sequenzen" in der Stdlib.
Optimierte Generatoren (z.B. SliceGen, RangeGen, IoLineGen) können zusätzlich existieren,
müssen aber weiterhin Generator[T] implementieren.
2.2 Collect[Src, Out]¶
Bedeutung:
- trennt lazy Erzeugung von Materialisierung,
- erlaubt mehrere Zielstrukturen ohne Sondersyntax je Ziel.
3. Standard-Combinators¶
Combinators sind normale Funktionen, die Generatoren in Generatoren transformieren.
Wichtiges Typdetail (siehe Section 2 / Type System): Generator[T] in Parameterposition ist
Sugar für ein implizites Generic G mit Constraint G: Generator[T].
-> Generator[U] bedeutet ein opaquer Rückgabetyp (kein dynamisches Trait-Object).
Konzeptionell:
def filter[T, G](g: G, pred: T -> bool) -> Generator[T]
where G: Generator[T]
def map[T, U, G](g: G, f: T -> U) -> Generator[U]
where G: Generator[T]
def mapMany[T, U, G](g: G, f: T -> Generator[U]) -> Generator[U]
where G: Generator[T]
def filterMap[T, U, G](g: G, f: T -> Option[U]) -> Generator[U]
where G: Generator[T]
In der Stdlib werden diese typischerweise als Wrapper-Generatoren implementiert
(z.B. MapGen[G, T, U]) oder via PullGen.unfold(...).
Kernnutzen: große Verarbeitungspfade bleiben als kleine, prüfbare Schritte lesbar.
4. Materialisierung und Sugar¶
|> []= Sammeln in Sequenzcontainer,|> {}= Sammeln in Mapping-Container.
Konzeptionelles Desugaring:
Die konkrete Instanzwahl erfolgt über Typkontext.
5. Fehlerintegration¶
Beispiel „fail-fast sammeln":
provide[T, E] Collect[Generator[Result[T, E]], Result[[T], E]] {
def collect(self: Generator[Result[T, E]]) -> Result[[T], E] { ... }
}
Wichtig: Fehlersemantik steckt im Typ/Collector, nicht im Basis-Generator-Protokoll.
6. Effektintegration¶
Generatoren dürfen effektvolle Quellen kapseln (z. B. Dateilesen), aber Abhängigkeiten
bleiben Funktionseigenschaft — als expliziter Parameter (imperativer Stil) oder über
Effect[R, E, D] im Rückgabetyp (Effect-Stil). Sie sind kein verstecktes Protokollverhalten.
// Imperativer Stil – Service als Parameter:
def grep(fs: Fs, path: Path, needle: str) -> [str] =
readLines(fs, path)
|> filter(line => line.contains(needle))
|> []
// Effect-Stil – lazy, komponierbar:
def grep(path: Path, needle: str) -> Effect[[str], FsError, Fs] =
readLines(path)
|> filter(line => line.contains(needle))
|> []
7. Normative Invarianten¶
Generator.nextliefertOption[T].- Materialisierung läuft über
Collect[Src, Out]. - Sugar ist reines Desugaring auf
Collect. - Fehlerpolitik wird explizit über Typen/Collector-Instanzen modelliert.
- Effekte bleiben sichtbar — über
Effect[R, E, D]im Rückgabetyp oder als explizite Service-Parameter.depsals Konstrukt existiert nicht.
8. Offene Punkte (Phase 2)¶
- weitere Collector-Ziele (Set, SmallArray, spezialisierte Buffer),
- Optimierungen (Fusion, Inlining, Allocation-Strategien),
- optionale Comprehension-Syntax als rein additive Sugar-Schicht.