Neander's Exceptionless Error Handling

Neander, a programming environment designed for specific host application interactions, operates without traditional exception handling mechanisms. This means no try, catch, or finally blocks. Instead, when a call to a host application's API is made, the result is analogous to Rust's Result type. It either returns the expected answer or a value representing the reason for failure. This design intentionally enforces a consistent response shape, regardless of whether an operation succeeds or fails. The system guarantees that every submission returns in the same format, providing a predictable interface for developers.

This approach to error management is a core tenet of Neander's philosophy, stemming from the foundational concept of isolation discussed previously in the series. The decision to eliminate exceptions simplifies control flow and makes error handling explicit. Instead of relying on the implicit unwinding of the call stack that exceptions trigger, Neander forces developers to acknowledge and handle potential failures at the point of API interaction. This makes the program's behavior more transparent and easier to reason about, especially in complex applications where unhandled exceptions can lead to unpredictable states.

The concept of an error in Neander is twofold: it is a value during program execution and a verdict after the program has completed its operation. These two states are intentionally constructed from the same underlying components, ensuring a unified perspective on failure. This design choice means that developers must consider errors not as exceptional events to be caught and discarded, but as integral parts of the program's data flow that need to be processed just like any successful result.

The Failable Type: Errors as Values

At the heart of Neander's error handling is the failable type. This type acts as a container for either a successful result or an error. When an API call is made, the return value will be one of these two states. This contrasts sharply with languages that use exceptions, where a successful call returns a value directly, and an error causes an exception to be thrown, interrupting the normal flow of execution.

Consider a typical scenario: a program needs to fetch user data from a remote service. In a language with exceptions, the code might look like this:

try {
  userData = api.getUser(userId);
  displayUser(userData);
} catch (NetworkError e) {
  logError(e);
} catch (UserNotFound e) {
  showNotFoundMessage();
}

In Neander, the equivalent operation would involve checking the returned failable type:

result = api.getUser(userId);
if (result.isSuccess()) {
  userData = result.getValue();
  displayUser(userData);
} else {
  errorReason = result.getError();
  logError(errorReason);
}

This explicit checking of the result forces developers to confront potential failures. It eliminates the possibility of accidentally ignoring an error condition because the exception was never caught. The structure of the failable type typically includes methods to check its state (e.g., isSuccess(), isError()) and to extract the contained value or error reason. The error reason itself is a structured value, providing specific details about what went wrong, rather than a generic exception object.

This design pattern is powerful because it brings errors into the domain of ordinary values. An error can be passed around, logged, transformed, or even returned by other functions. This makes error handling a first-class citizen in the programming model, rather than an afterthought or a special control-flow mechanism. The consistency in return shape also means that the host application can reliably process the outcome of any Neander program, whether it completed successfully or encountered an issue.

Operators and Type Markers

Neander employs a minimal set of operators and type markers to interact with failable types. These are designed to be intuitive and to enforce the explicit handling of errors. While the exact syntax may vary, the conceptual tools are consistent:

  • Type Markers: These are used to distinguish between successful results and error values within the failable type. They are not directly manipulated by the developer but are fundamental to how the system differentiates states.
  • Extraction Operators: These operators are used to safely extract the success value or the error reason from the failable type. Using these operators typically requires the developer to have already checked the state of the failable type, or the operator itself may perform a check. For instance, an operator to get the success value might panic or return a default if called on an error state, though this would be contrary to Neander's explicit error handling philosophy. More likely, these operators are designed to work only when the type is in the expected state.
  • Conditional Logic Operators: These allow for branching based on the success or failure state of the failable type. They are the programmatic equivalent of the if/else structure shown previously, but potentially more concise or integrated into the language's syntax.

The goal of these constructs is to make the code demonstrably safe. A Neander program that compiles and runs without explicit error handling for a failable type is unlikely to exist, as the type system and the operators would prevent it. This is akin to how a compiler might prevent a program from accessing an uninitialized variable. The system forces the programmer's hand, ensuring that failure paths are considered.

The "So What?" Perspective

Developer Impact

Developers interacting with Neander must abandon traditional try/catch blocks. Embrace the failable type, where errors are explicit values to be checked and handled. This requires a shift in mindset towards explicit error propagation and processing, similar to Rust's Result or Go's multi-value returns.

Security Analysis

Neander's design inherently reduces certain classes of vulnerabilities related to unhandled exceptions, such as unexpected program termination or state corruption. However, the structured error values themselves could potentially leak sensitive information if not carefully managed by the host application.

Founders Take

This approach to error handling can lead to more robust and predictable applications, reducing costly runtime failures. It signals a commitment to developer experience and system stability, which can be a competitive advantage for platforms built on Neander.

Creators Insights

For creators building with Neander, the lack of exceptions means workflows must explicitly account for potential API failures. This can lead to more resilient interactive experiences, but requires careful design to avoid interrupting user flows with error handling prompts.

Data Science Perspective

The structured nature of Neander's error values provides consistent data points for failure analysis. This allows for more precise telemetry and debugging, enabling data scientists to build better models for predicting and diagnosing issues within Neander-based applications.

Sources synthesised

Share this article