Zum Inhalt

JDL — Partielle Applikation

Status: Konsolidierungsentwurf, Arbeitsstand Version: 0.1.3 Datum: 2026-10-02 Geltungsbereich: Funktionen teilweise aufrufen — zur Laufzeit mit _, in der Meta-Phase mit Call(f) — und was dabei mit Typ, Row, Fehlern und Blueprints geschieht Quellen: Entscheidungen aus den Arbeitsgesprächen (Stand 2026-10-02), jdl-blueprint-spec 0.5.2 §3.5, §3.6, §3.7, §5.4, §8.1, §9.1, jdl-meta-phase 0.3.1, jade-compiler-db-spec §9.3, jdl-effect-rows-und-throw, wie-jade-uebersetzt §4.3, §4.4, §4.9


0. Über dieses Dokument

0.1 Kennzeichnung

Marke Bedeutung
[N] bestehend normativ belegt, Fundstelle genannt
[E] entschieden, aber noch in keinem Quelldokument ausgeschrieben
[A] aus bestehendem Material ableitbar, aber nirgends ausgeschrieben
[V] Vorschlag, ausgearbeitet, aber nicht entschieden
[O] offen

Dieses Dokument ist neu. Fast alles darin trägt [E], weil es in Arbeitsgesprächen entschieden, aber bisher nirgends aufgeschrieben wurde.

0.2 Syntax

Alle Beispiele zeigen Form, nicht Anwendung, und tragen // spekulativ. Entschieden ist nur das, was im Text mit [E] steht — etwa dass _ die Lücke markiert —, nicht die übrige Schreibweise der Beispiele.

Durchgehendes Beispiel. Eine Funktion, die eine E-Mail verschickt:

// spekulativ
def send(to: str, subject: str, body: Html) ->{Mailer, Throw[MailError]} Unit
  • send hat drei Parameter.
  • In der Row stehen der Service Mailer und ein möglicher Fehler MailError.
  • Die Funktion gibt nichts zurück (Unit).

0.3 Herkunft

Zu diesem Thema gab es in den Arbeitsgesprächen eine formale Notation mit Regeln für die Laufzeitform, die Meta-Form und ihre Kohärenz. Sie ist nicht als Dokument erhalten. Die Regeln in §6 sind aus den festgehaltenen Entscheidungen neu formuliert, nicht abgeschrieben. Eine formale Fassung nach jdl-formale-notation-style-guide steht aus (§5).


1. Worum es geht

Eine Funktion partiell anwenden heißt: einige Argumente jetzt festlegen, die übrigen später liefern. Heraus kommt eine neue Funktion, die nur noch die offenen Parameter erwartet.

Das braucht man, wenn eine Funktion an einer Stelle vorbereitet und an einer anderen aufgerufen wird: ein Versand, dessen Betreff feststeht, dessen Empfänger aber erst in einer Schleife bekannt wird.

JDL bietet dafür einen Gedanken in zwei Phasen:

Laufzeit     send(to = _, subject = "Hallo", body = _)       ergibt eine Funktion
Meta-Phase   Call(send) & { subject = "Hallo" }             ergibt einen Blueprint

Beide bedeuten dasselbe (§4). Die Laufzeitform ist die schnelle Schreibweise im Code, die Meta-Form die Schreibweise in Blueprints, wo sich die festgelegten Werte per Profil anpassen lassen.

Was es nicht gibt: Currying [E]. Eine Funktion mit drei Parametern ist nicht automatisch eine Kette aus drei Funktionen mit je einem Parameter. Partielle Applikation passiert nur dort, wo jemand sie mit _ ausdrücklich verlangt.


2. Zur Laufzeit: die Lücke _

2.1 Woran man sie erkennt [E]

Steht in einem Aufruf ein _ an der Stelle eines Arguments, ist der Aufruf eine partielle Applikation. Ohne _ ist er ein gewöhnlicher Aufruf.

// spekulativ
val notify = send(to = _, subject = "Wochenrückblick", body = news::weekly)
notify(to = "anna@example.com")
  • Zeile 2: to bleibt offen, subject und body werden festgelegt. notify ist eine Funktion.
  • Zeile 3: notify wird mit dem fehlenden Argument aufgerufen. Erst jetzt wird die E-Mail verschickt.

Ein fehlendes Argument ist ein Fehler [E]. Wer ein Argument weglässt, ohne _ zu schreiben, bekommt keine partielle Applikation, sondern einen Compilerfehler. Die Lücke muss man sehen können.

Das ist dieselbe Regel wie überall in Jade: kein Automatismus, der rät, was gemeint war.

2.2 Die Lücke gehört zum innersten Aufruf [E]

Stehen Aufrufe ineinander, gehört ein _ zu dem Aufruf, der ihn unmittelbar umschließt.

// spekulativ
print(format(_, width = 10))
  • _ steht in format(...). format wird partiell angewendet und ergibt eine Funktion.
  • print bekommt diese Funktion, nicht einen formatierten Text.

Wer „print soll später aufgerufen werden“ meint, muss die Lücke in print setzen. Die Regel ist bewusst die einfachste denkbare; sie macht den Ausdruck von innen nach außen lesbar.

2.3 Festgelegte Argumente werden sofort ausgewertet [E]

Ein festgelegtes Argument wird ausgewertet, wenn die partielle Applikation entsteht, nicht erst beim späteren Aufruf.

In §2.1 wird news::weekly also einmal ausgewertet, wenn notify entsteht. Ruft man notify hundertmal auf, wird es nicht hundertmal neu berechnet.

Das macht das Verhalten vorhersagbar: Was festgelegt ist, steht fest.

2.4 Row und Fehler wandern in den Ergebnistyp [E]

Die partielle Applikation selbst führt nichts aus. Die Row der Funktion geht deshalb unverändert in den Typ des Ergebnisses über:

send     (to: str, subject: str, body: Html) ->{Mailer, Throw[MailError]} Unit
notify   (to: str)                           ->{Mailer, Throw[MailError]} Unit
  • Die festgelegten Parameter verschwinden aus der Parameterliste.
  • Die Row bleibt vollständig. Wer notify aufruft, braucht Mailer und muss mit MailError rechnen — genau wie beim Aufruf von send.

Nichts verschwindet heimlich. Man sieht an der Signatur des Ergebnisses, was der spätere Aufruf braucht.

Fehler beim Festlegen selbst gehören dagegen zur Stelle, an der die Applikation entsteht. Wirft die Auswertung eines festgelegten Arguments (§2.3), trägt die umgebende Funktion diesen Fehler in ihrer eigenen Row [A].

2.5 Was man nicht festlegen kann [E]

Geliehene Werte. Ein geliehener Wert darf die Stelle nicht überleben, an der er geliehen wurde. Eine partielle Applikation kann aber länger leben. Einen geliehenen Wert festzulegen, ist deshalb ein Fehler. Wer den Wert festlegen will, übergibt eine Kopie (.clone()).

var-Parameter. Ein var-Parameter gibt der Funktion Schreibzugriff auf eine Bindung des Aufrufers. Diese Bindung gibt es erst beim eigentlichen Aufruf. var-Parameter bleiben deshalb immer offen und müssen mit _ stehen.

2.6 Namen bleiben erhalten [E]

Die offenen Parameter behalten ihre Namen. notify erwartet to, nicht „das erste Argument“.

Benannte Argumente sind bei partieller Applikation die bevorzugte Schreibweise, weil sonst schwer zu sehen ist, welche Stelle offen bleibt.

[O] Ob benannte Argumente ab zwei Parametern Pflicht werden, ist offen.

2.7 Festgelegte Werte gehören nicht zum Typ [E]

Zwei partielle Applikationen von send mit verschiedenem Betreff haben denselben Typ. Welche Werte festgelegt sind, ist Inhalt, nicht Typ.

Wer einer solchen Funktionsform einen Namen geben will, schreibt einen Typalias:

// spekulativ
type Notifier = (to: str) ->{Mailer, Throw[MailError]} Unit
  • Notifier ist nur ein anderer Name für diese Signatur. notify aus §2.1 ist ein Notifier, ebenso jede andere Funktion mit dieser Signatur.

3. In der Meta-Phase: Call(f)

3.1 Was Call baut [E]

Call ist eine Meta-Funktion. Sie nimmt eine Funktion und baut daraus einen Blueprint: eine Beschreibung des Aufrufs, in der jeder Parameter ein Feld ist.

// spekulativ
type WeeklyNews : Call(send) & {
    subject = "Wochenrückblick"
}
  • Call(send) baut in der Meta-Phase einen Blueprint aus der Signatur von send.
  • & { ... } ist ein gewöhnliches Blueprint-Profil (jdl-blueprint-spec §3.6). Es legt subject fest.
  • to und body bleiben offen.

Anders als in §2.1 wird body hier nicht festgelegt. Der Inhalt eines Wochenrückblicks entsteht erst zur Laufzeit. In der Meta-Form würde er beim Übersetzen ausgewertet, und jede Woche ginge derselbe Rückblick hinaus (§4).

Der gebaute Blueprint hat:

  • ein Feld target, das auf send verweist,
  • für jeden Parameter ein Feld mit demselben Namen. Es ist ein var mit dem Vorgabewert „offen“, sodass ein Profil es festlegen kann.
  • var-Parameter der Funktion werden zu val-Feldern mit dem Wert „offen“. Kein Profil kann sie festlegen — dieselbe Regel wie §2.5.

Welchen Typ ein solches Feld hat, um „offen oder ein Wert“ auszudrücken, ist in den Arbeitsgesprächen als Slot[T] skizziert, aber nicht festgelegt (§5).

3.2 Die Signatur von Call [E]

// spekulativ
def Call(f: FunctionSubject) ->Meta{CompilerDb, Throw[DiagnosticSet]} TypePatch
  • Meta am Pfeil: Die Funktion läuft beim Übersetzen (jdl-meta-phase §2.1).
  • CompilerDb: Sie liest die Signatur von f aus der CompilerDB.
  • Throw[DiagnosticSet]: Sie kann mit Diagnosen scheitern.
  • Sie liefert einen Patch für einen neuen Typ, keinen fertigen Typ und keinen Wert.

Call schreibt nicht selbst [E]. Wie jede Meta-Funktion darf sie die CompilerDB nur über Patches ändern (jdl-meta-phase §4.7, MP-8). Ihr Ergebnis ist eine Änderungsabsicht: „lege diesen Blueprint-Typ an“. Den Weg gibt es schon; db.createStruct liefert dieselbe Art von Patch (jade-compiler-db-spec §9.3, dort noch unter dem Namen TypeDescriptorPatch).

Den Namen gibt die Deklaration: In type WeeklyNews : Call(send) & { ... } heißt der neue Typ WeeklyNews. Erst der Materializer prüft den Patch und legt den Typ an.

Eine Meta-Funktion, die einen Typ erzeugt, steht damit dort, wo sonst ein Typ steht. Das ist gewollt: Der Typ entsteht durch eine gewöhnliche Funktion, nicht durch ein eigenes Sprachmittel.

3.3 Die CallEngine [E]

Den Blueprint wertet eine CallEngine aus, wie jede andere Engine (jdl-blueprint-spec §9). Dass describe den CallDescriptor als getypten Wert liefert, folgt der Form der RuntimeEngine in jdl-blueprint-spec §9.1; diese Form ist dort noch ein Vorschlag [V].

describe      Blueprint                ->  CallDescriptor             in der CompilerDB
instantiate   Excerpt[CallDescriptor]  ->  die verbleibende Funktion  zur Laufzeit

Der CallDescriptor hält vier Dinge:

target   Verweis auf die Funktion
fixed    die festgelegten Argumente
open     die offenen Parameter mit Namen und Typ
row      die Row der Funktion

target ist ein gewöhnlicher Verweis [E]. Innerhalb des Artefakts zeigt er wie jeder Verweis auf den Excerpt-Hash seines Ziels (wie-jade-uebersetzt §4.4), also auf die Funktion samt Rumpf. Ändert sich der Rumpf von send, ändert sich dieser Hash und mit ihm der Hash des CallDescriptors. Das ist richtig so: Der Excerpt zeigt dann wirklich auf anderen Code. Über Artefaktgrenzen gilt stattdessen §3.4.

Er ist serialisierbar, wie jeder Descriptor. Die festgelegten Werte müssen es dafür ebenfalls sein (jdl-blueprint-spec §7.2). Den Descriptor selbst gibt es nur in der CompilerDB; instantiate bekommt seinen Excerpt (jdl-blueprint-spec §9.1, BP-22). Für einen Call überleben alle vier Felder die Projektion, weil instantiate jedes davon braucht.

instantiate liefert eine Funktion mit genau den offenen Parametern und der vollständigen Row — denselben Typ, den die Laufzeitform aus §2.4 liefern würde.

3.4 Was Call annimmt [E]

Keine Meta-Funktionen. Eine Funktion mit Meta am Pfeil kann nicht an Call übergeben werden. Ein Blueprint beschreibt etwas, das zur Laufzeit geschieht, und eine Meta-Funktion gibt es zur Laufzeit nicht (jdl-meta-phase §2.2).

Trusted Core: Call darf aus der CompilerDB nur die Signatur von f lesen, nie den Rumpf. Dann hängt die Form des gebauten Blueprints — seine Felder und ihre Typen — nur von der Signatur ab. Ändert sich das Innere von send, bleibt WeeklyNews dem Namen und der Form nach derselbe Typ. Sein Hash ändert sich trotzdem, weil target auf den neuen Rumpf verweist (§3.3). Neu geprüft werden muss deshalb nur, was mehr als die Form liest. Läse Call den Rumpf, hinge an jeder Implementierungsänderung eine neue Typprüfung aller Benutzer.

Einen Verweis auf send zu setzen, heißt nicht, seinen Rumpf zu lesen: Call übernimmt den Hash, ohne den Inhalt dahinter auszuwerten.

Aufrufe über Artefaktgrenzen [E]. Soll ein durch Call(f) beschriebener Aufruf außerhalb des eigenen Artefakts ausgeführt werden, muss f über eine exportierte Schnittstelle erreichbar sein. Der Zielverweis wird bei der Zulassung über Name und Schnittstellen-Hash geprüft (wie-jade-uebersetzt §4.9). Der Hash umfasst die Signatur einschließlich Row; er ist der Excerpt-Hash des Exports, nicht der der Funktion samt Rumpf.

Gebunden wird die am Ziel installierte Implementierung dieser Schnittstelle: der Rumpf, der im Ziel-Artefakt auf diesen Export verweist (wie-jade-uebersetzt §4.9). Der Verweis garantiert Schnittstellenidentität, nicht Implementierungsidentität.

Call(f)
 |
 +-- lokal -----------------> die konkrete Funktion f
 |
 +-- über Artefaktgrenze
      |
      v
     exportierte Schnittstelle
      |
      +--> Name
      |
      +--> Schnittstellen-Hash, einschließlich Row
      |
      v
     Zulassung im Ziel-Artefakt
      |
      v
     dort installierte Implementierung

target im CallDescriptor sagt über eine Artefaktgrenze also nicht „führe genau diesen Code aus“, sondern „führe eine zugelassene Implementierung dieser Schnittstelle aus“. Das folgt aus der allgemeinen Regel für Verweise über Artefaktgrenzen: Sie zeigen nur auf Schnittstellen (wie-jade-uebersetzt §4.9). Die Implementierung gehört dort nicht zur Identität des beschriebenen Aufrufs.

Ist f nicht exportiert, kann der Aufruf nur innerhalb seines Artefakts ausgeführt werden.

3.5 Verwendung in einem Context [E]

Ein Call-Blueprint kann Feld eines anderen Blueprints sein, etwa eines Contexts. Die Funktion, die ihn benutzt, nennt ihn in ihrer Pipe-Liste, und ihre Row muss die Row des Aufrufs mittragen:

// spekulativ
type NewsletterContext : blueprint {
    services = [Mailer, Subscribers]
    var weekly: WeeklyNews
    var run: | .services.*, .weekly | (args: [str]) ->{Subscribers, Mailer, Throw[MailError]} ExitCode
}
  • Zeile 3: Der Context registriert zwei Services.
  • Zeile 4: weekly ist ein Feld vom Typ WeeklyNews, dem vorbereiteten Versand aus §3.1. Damit ist es ein verschachtelter Blueprint (jdl-blueprint-spec §3.7). Ob es ohne = ein Pflichtfeld ist, ist dort offen (Punkt 22).
  • Zeile 5: run bietet alle Services an und nimmt das Feld weekly mit (jdl-blueprint-spec §5.4). Die Row nennt Subscribers, weil run die Abonnenten liest, und Mailer samt MailError, weil ein Aufruf von .weekly am Ende send aufruft.

Was run von weekly bekommt [E]. Weil weekly ein verschachtelter Blueprint ist, wird in run kein Wert eingebacken, sondern ein Verweis auf den Excerpt seines CallDescriptors. Zur Laufzeit steht dort die Funktion, die das instantiate der CallEngine geliefert hat (jdl-blueprint-spec §3.7). run ruft sie mit den offenen Argumenten to und body auf.

Das ist zulässig, weil die Funktion keine Autorität trägt: Sie braucht Mailer beim Aufruf, und den muss run in seiner Row haben. Ein Blueprint, dessen instantiate ein Handle liefert, dürfte so nicht in der Pipe-Liste stehen (jdl-blueprint-spec §3.7).

Fehlte Mailer in der Row, wäre das ein Fehler: Die Row von weekly wandert beim Aufruf in die Row von run (§2.4), und run hätte sie nicht angegeben.


4. Kohärenz [E]

Laufzeitform und Meta-Form sind derselbe Gedanke und müssen dasselbe Ergebnis liefern:

send(to = _, subject = s, body = b)

    ergibt denselben Typ und dasselbe Verhalten wie

instantiate auf dem Excerpt von  describe(Call(send) & { subject = s, body = b })

Das heißt konkret:

  • gleiche offene Parameter, mit gleichen Namen und Typen,
  • gleiche Row,
  • gleiche Regeln für geliehene Werte und var-Parameter (§2.5).

Ein Unterschied bleibt und ist gewollt: Die Laufzeitform wertet festgelegte Argumente zur Laufzeit aus (§2.3), die Meta-Form beim Übersetzen. In der Meta-Form können deshalb nur Werte festgelegt werden, die in der Meta-Phase feststehen und serialisierbar sind.


5. Offene Punkte

# Punkt Abschnitt
1 Benannte Argumente ab zwei Parametern Pflicht? §2.6
2 Call(send) oder Call[send] §3.1
3 Typ eines offenen Feldes (Slot[T]) und seine Umwandlung §3.1
4 Formale Fassung der Regeln nach dem Style Guide §6
5 Verhältnis zu einem verbrauchenden Parametermodus §2.5
6 Wie jeder durch Call erzeugte Typ an die CallEngine gebunden wird §3.2, §3.3

Punkt 2. Die Tendenz geht zu runden Klammern. Die Regel dahinter wäre: Runde Klammern beim Aufruf genau dann, wenn sie auch bei der Deklaration stehen — Call ist eine Funktion und wird deshalb wie eine aufgerufen. Das Vorbild ist Zig, wo ArrayList(i32) ein Typ ist, der durch einen Funktionsaufruf entsteht. Eckige Klammern blieben Typparametern vorbehalten. Entschieden ist das nicht; dieselbe Frage stellt sich bei Refinements wie ExtensibleVia(...).

Punkt 3. Das Feld muss zwei Zustände ausdrücken: offen oder festgelegt mit einem Wert vom Typ T. Wie ein festgelegter Wert in ein Slot[T]-Feld kommt — ob subject = "Hallo" direkt geht oder eine Umwandlung braucht —, ist offen. Für Anfänger sollte es direkt gehen.

Punkt 5. Ein Parametermodus, der einen Wert verbraucht statt ihn zu leihen, ist als offene Sprachfrage geführt. Kommt er, muss feststehen, ob ein verbrauchter Wert festgelegt werden kann. Naheliegend ist: ja, weil die partielle Applikation ihn dann besitzt.

Punkt 6. Jedes Call(f) erzeugt einen neuen Typ, und für jeden Blueprint-Typ gibt es höchstens eine Engine (jdl-blueprint-spec §9.4). Wie das provide der CallEngine an jeden dieser Typen kommt, steht nirgends. Naheliegend: Der Patch von Call trägt diese Bindung mit.


6. Normative Regeln

PA-1   Ein Aufruf mit mindestens einem `_` an Argumentstelle ist eine
       partielle Applikation. Ohne `_` ist er ein gewöhnlicher Aufruf;
       ein fehlendes Argument ist ein Compilerfehler.
       [E]

PA-2   Ein `_` gehört zum innersten ihn umschließenden Aufruf.
       [E]

PA-3   Festgelegte Argumente werden ausgewertet, wenn die partielle
       Applikation entsteht.
       [E]

PA-4   Das Ergebnis ist eine Funktion mit den offenen Parametern in
       ursprünglicher Reihenfolge, mit ihren Namen, und mit der
       unveränderten Row der angewendeten Funktion.
       [E] / [A] für die Reihenfolge

PA-5   Geliehene Werte können nicht festgelegt werden. `var`-Parameter
       bleiben immer offen.
       [E]

PA-6   Festgelegte Werte sind nicht Teil des Typs.
       [E]

PA-7   Es gibt kein automatisches Currying.
       [E]

CALL-1 `Call` ist eine Meta-Funktion. Sie liefert aus der Signatur
       einer Funktion einen Patch für einen Blueprint-Typ mit einem Feld
       `target` und einem Feld je Parameter. Parameter werden zu
       `var`-Feldern mit Vorgabe „offen“, `var`-Parameter zu `val`-Feldern
       mit Wert „offen“. Den Namen gibt die Deklaration, angelegt wird
       der Typ vom Materializer.
       [E]

CALL-2 `Call` liest nur die Signatur der Funktion, nie ihren Rumpf.
       Funktionen mit `Meta` am Pfeil sind nicht zulässig.
       [E]

CALL-3 Die CallEngine erzeugt einen serialisierbaren CallDescriptor aus
       target, fixed, open und row. instantiate bekommt dessen Excerpt
       und liefert die verbleibende Funktion. target ist ein
       gewöhnlicher Verweis über den Excerpt-Hash des Ziels.
       [E] / [V] für die getypte Form von describe

CALL-4 Laufzeitform und Meta-Form sind kohärent: gleiche offene
       Parameter, gleiche Row, gleiche Regeln nach PA-5. In der
       Meta-Form sind nur Werte festlegbar, die in der Meta-Phase
       feststehen und serialisierbar sind.
       [E]

CALL-5 Über Artefaktgrenzen darf ein Call nur eine exportierte
       Schnittstelle treffen. Geprüft wird bei der Zulassung über Name
       und Schnittstellen-Hash einschließlich Row. Gebunden wird die am Ziel
       installierte Implementierung; garantiert ist Schnittstellen-,
       nicht Implementierungsidentität.
       [E]

Änderungsprotokoll

0.1.3 (2026-10-02) — Kohärenzprüfung. §1: Laufzeitform der Gegenüberstellung lässt body offen wie die Meta-Form. §3.1: WeeklyNews legt nur subject fest; ein Wochenrückblick als festgelegter Wert hätte beim Übersetzen festgestanden. §3.2: Call liefert ein TypePatch, den Namen gibt die Deklaration, angelegt wird über den Materializer; CALL-1 angepasst. §3.3: target ist ein gewöhnlicher Verweis über den Excerpt-Hash; Hinweis, dass die getypte Form von describe in der Blueprint-Spec ein Vorschlag ist; CALL-3 angepasst. §3.4: Begründung für „nur die Signatur lesen“ auf die Form des Blueprints umgestellt; der Hash von WeeklyNews ändert sich mit target, die Form nicht. „Schnittstellen-Hash“ statt „Signatur-Hash“, als Excerpt-Hash des Exports; CALL-5 angepasst. Neuer offener Punkt 6. §3.5: var weekly: WeeklyNews statt := (:= nur für Werte); was run von einem verschachtelten Blueprint bekommt. §3.4: Die installierte Implementierung ist der Rumpf, der im Ziel-Artefakt auf den Export verweist. §3.5: Warum die Funktion aus weekly über die Pipe-Liste kommen darf: Sie trägt keine Autorität. TypeDescriptorPatch heißt TypePatch (Nachtrag A8).

0.1.2 (2026-10-02) — §3.3, §4 und CALL-3: instantiate bekommt den Excerpt des CallDescriptors, nicht den Descriptor selbst, entsprechend jdl-blueprint-spec 0.5.1.

0.1.1 (2026-09-29) — §3.4 um Aufrufe über Artefaktgrenzen ergänzt: nur über exportierte Schnittstellen, geprüft über Name und Signatur-Hash, garantiert Schnittstellen- statt Implementierungsidentität. Regel CALL-5.

0.1.0 (2026-09-29) — Erste Fassung. Entscheidungen aus den Arbeitsgesprächen zur partiellen Applikation zur Laufzeit (_) und in der Meta-Phase (Call) erstmals aufgeschrieben. Die formalen Regeln aus den Gesprächen sind nicht erhalten und hier neu formuliert; eine formale Fassung steht aus. Das Beispiel NewsletterContext in §3.5 führt die vollständige Row ->{Subscribers, Mailer, Throw[MailError]}; eine frühere Skizze im Gespräch führte nur Throw[MailError] und war damit unvollständig.