Zum Inhalt

05 — Casts, Try und Fallible Boundaries

Status: Konsolidierungsentwurf


1. CastTo bleibt ein Protocol

Konversion ist weiterhin eine gerichtete Relation zwischen Source und Target über CastTo[T].

provide CastTo[UserResponse] for User {
    def castTo(self) -> UserResponse = UserResponse {
        id:    self.id
        name:  self.name
        email: self.email
    }
}

2. Cast-Modus gehört zur Relation

Ein Cast hat genau einen Modus:

Lossless
    total, verlustfrei, implizit zulässig

Narrowing
    total, aber verlustbehaftet, nur explizit

Try[E]
    fallibel, liefert Result[T, E]

Empfohlene neue Schreibweise:

provide CastTo[UserSummary] for User
    :> CastMode(Narrowing)
{
    def castTo(self) -> UserSummary = UserSummary {
        name: self.name
    }
}

provide CastTo[UserId] for User
    :> CastMode(Try[ConvError])
{
    def castTo(self) -> Result[UserId, ConvError] =
        if self.id.value.isEmpty() then
            Err(ConvError.InvalidId)
        else
            Ok(self.id)
}

Alternativ kann CastMode als Associated Meta im provide-Block normalisiert werden. Wichtig ist nicht die Oberflächensyntax, sondern die Semantik:

CastMode ist Eigenschaft der Relation User -> UserId.
Nicht Eigenschaft des Zieltyps UserId.

3. try als fallible Scope

try erzeugt einen fallible Boundary-Scope.

val parsed = try {
    val id  =? raw.id -> UserId
    val age =? raw.age -> i32

    ParsedUser { id, age }
}

parsed hat den Typ:

Result[ParsedUser, E]

abhängig von den Fehlern im Block.


4. =? propagiert zur nächsten Boundary

=? bedeutet nicht mehr hart „return aus Funktion“.

Regel:

`=?` propagiert zur nächsten umschließenden Propagation Boundary.

Propagation Boundaries:

- try-Block
- Funktion mit passendem fallible Rückgabetyp
- spätere fallible Scopes

ASCII:

def f(...) -> Result[T, E] {
    val r = try {
        val x =? op1()
        val y =? op2(x)
        y
    }

    val z =? r
    Ok(z)
}

inneres =? -> verlässt try-Block
äußeres =? -> verlässt Funktion

5. Kein redundantes try bei sofortiger Propagation

Nicht nötig:

val id =? try raw.id -> UserId

Wenn der Ausdruck sofort mit =? konsumiert wird, reicht:

val id =? raw.id -> UserId

Innerhalb eines try-Blocks propagiert =? nur bis zum Block.

val parsed = try {
    val id =? raw.id -> UserId
    ParsedUser { id }
}

6. Fallible Ergebnis behalten

Wenn das fallible Ergebnis als Wert behalten werden soll, wird try verwendet.

val idResult = try {
    raw.id -> UserId
}

oder als Kurzform, falls später zugelassen:

val idResult = try raw.id -> UserId

MVP-Empfehlung:

Nur `try { ... }` normativ machen.
`try expr` später als Sugar definieren.

7. Weak -> Strong

Weak-Upgrades passen in dieselbe Kategorie.

val parentOpt = try {
    val parent =? node.parent.upgrade()
    parent
}

Wenn upgrade() Option[Strong[Node]] liefert, endet der try-Block bei None mit None, nicht mit einem Funktions-Return.


8. Normative Regeln

F-1: `try { ... }` ist ein Ausdruck.
F-2: Ein try-Block erzeugt eine Propagation Boundary.
F-3: `=?` propagiert immer nur zur nächsten Boundary.
F-4: Innerhalb einer Funktion ohne try-Block ist die Funktion die nächste Boundary.
F-5: `try` selbst führt keinen Runtime-Return aus der umgebenden Funktion aus.
F-6: `=?` darf nur auf propagierbare Container angewendet werden.
F-7: Fallible Casts liefern Result[T, E].
F-8: Weak-Upgrades liefern Option[T] und können über try-Boundaries lokal behandelt werden.