⚠️

Dubious Content

This content is aspirational or placeholder. Syntax and semantics may be incorrect or subject to change.

Optional Branches

Build flexible event-driven systems with selective handling. Optional branches use the `|?` catch-all to handle supplementary outcomes generically, enabling patterns like event pumps without exhaustive handling.

What Are Optional Branches?

By default, Koru requires handlers to handle every branch an event can return. Optional branches, marked with ?, can be caught by the |? catch-all pattern:

The success branch is required—handlers must handle it explicitly. The ?warning and ?debug branches are optional—handlers can use |? to catch them generically. Without |?, if an optional branch fires, execution stops.

Why Optional Branches?

Supplementary Information

Events can provide rich diagnostic information—warnings, debug output, profiling stats—that handlers only care about in specific contexts. Optional branches let you include this without forcing every handler to deal with it.

Zero-Cost Abstractions

Write richly instrumented procs without performance cost. When handlers ignore optional branches, the compiler eliminates that code entirely. You get debug-rich code in development, zero-cost in production.

Context-Specific Handling

Different handlers legitimately care about different outcomes. A validation event might return valid data, warnings, and detailed error context—but not every handler needs all three. Optional branches let each handler choose what matters for its use case.

The Event Pump Pattern (THE Use Case)

This is THE motivating use case for optional branches: wrapping event-based systems like WIN32, SDL, or other event APIs with dozens of event types where you only care about a subset.

Why this works: The |? catch-all satisfies the branch interface for all unhandled optional branches. When window_event or timer_event fire, they're caught by |? and the loop continues.

Without |?: The loop would stop the first time an unhandled event fires, making the pattern impossible. You'd need exhaustive handling of all 50+ event types, which defeats the purpose.

Mixing Explicit Handling with |?

You can mix explicit handling of specific optional branches with |? catch-all for the rest. This is the most realistic real-world pattern:

Handler 1 explicitly handles the warning branch because it cares about warnings. Handler 2 uses |? to catch all optional branches generically. Both are valid patterns.

Selective Handling

Events can have multiple optional branches. Different handlers can choose different subsets to handle explicitly, using |? for the rest:

This is the real-world pattern: APIs provide multiple supplementary branches (warnings, debug info, profiling stats), and each handler explicitly handles the ones it cares about, using |? to catch the rest.

Zero-Cost Abstractions

The Koru compiler eliminates code for unhandled optional branches. This means you can write rich, instrumented procs without worrying about production performance:

When the handler omits the profile branch, the compiler eliminates the timer instrumentation entirely. You get debug-rich procs in development and zero-cost in production.

Shape Checking Still Applies

Optional branches can be omitted, but when you do handle them, shape checking still applies:

"Optional" means "can be omitted," not "can be misused." The compiler still verifies that payload types match when branches are handled.

Why |? Is NOT the F# Discard Pattern

This is fundamentally different from F#'s _ discard, which silently catches ALL future cases (including important ones). Koru's |? catches OPTIONAL branches ONLY:

The key difference: When you add a REQUIRED branch to an event, ALL handlers without that branch fail to compile. Cascade compilation errors force you to consider the new branch everywhere.

When you add an OPTIONAL branch, handlers with |? continue working (correct! it's optional supplementary info). The |? catch-all preserves exhaustive handling for required branches while allowing flexibility for optional ones.

Best Practices

When to Use Optional Branches

  • Supplementary information: Warnings, debug output, profiling stats that aren't core to the event's purpose
  • Context-specific handling: When different handlers legitimately care about different aspects of the same operation
  • Instrumentation: Profiling, tracing, or diagnostic data that shouldn't impact production code

When NOT to Use Optional Branches

Don't make primary outcomes optional. If a branch represents a core result, keep it required to ensure handlers address it. And don't use optional branches to avoid updating handlers when evolving an API—Koru's exhaustive handling is how you ensure API changes are complete. Cascade compilation errors are a feature, not a bug!

Recommended Patterns

Related Tutorials