neoCLR

IMPLEMENTATION NOTE · OPEN DESIGN

Make expected outcomes visible.

Option represents absence; Result represents recoverable failure. Raven patterns extract case values, and ? propagates an outcome to the caller.

Preview 8 foundation · Development additions below. Patterns and propagation are available in Preview 8. The operator section describes newer development work requiring matching references and libraries. These APIs remain open to feedback.

Option, Result and propagation in Raven.

func Normalize(value: int) -> Result<int, OverflowError> {
    let amount = Math.Abs(value)?
    WriteLine("Continued")
    return Result<int, OverflowError>.Ok(amount)
}

Normalize(-42) produces Ok(42). The minimum Int32 value cannot be made positive, so the sample propagates OverflowError. It prints Continued and 42 for the first call, then Overflow propagated for the second.

Complete executable sample → · VS Code setup →

Composing outcomes.

Development after Preview 8. These operators are implemented on main and need matching development references and the System library. They are not in the published Preview 8 packages; names and contracts remain open to feedback.

Map transforms a successful value. Then chains an operation that already returns an outcome. OrElse supplies a fallback only when needed. Patterns remain useful when you want to handle each case directly.

import System.*
import System.Collections.*
import System.Linq.*
import System.Option.*
import System.Result.*
import System.Console.*

func Main() {
    let input: Option<int> = Some(21)
    let answer = input.Map(value => value * 2)
        .Filter(value => value > 0)

    match answer {
        Some(let value) => WriteLine(value)
        None => WriteLine("No answer")
    }

    let result = answer.OkOr("Missing input")
        .Then((value: int) -> Result<int, string> => Ok(value + 1))
    match result {
        Ok(let value) => WriteLine(value)
        Error(let error) => WriteLine(error)
    }

    let absent: Option<int> = None
    let fallback = absent.OrElse(() => Some(7))
    WriteLine(fallback.UnwrapOr(0))

    let failure: Result<int, string> = Error("Unavailable")
    let recovered = failure.MapError(message => String.Concat("Read: ", message))
        .OrElse(message => {
            WriteLine(message)
            return Ok(9)
        })
    WriteLine(recovered.UnwrapOr(0))

    for value in answer.ToIterable() {
        WriteLine(value)
    }
}

This example prints 42, 43, 7, Read: Unavailable, 9 and 42 on separate lines. The explicit Then callback signature works around a current compiler inference limitation; the other callbacks use inferred types. The recovery callback only runs for Error. ToIterable explicitly converts a successful value to a one-element collection; None and Error become empty collections.

Complete executable sample → · Expected output → · Development setup →

Errors are ordinary values.

Development after Preview 8. The legacy System.Error message wrapper is removed. Use a string for a simple message or a dedicated error type when callers need to distinguish cases. Result.Error is the union case; it does not require an error base class.

With imported Result cases and an expected type, write let failure: Result<int, string> = Error("Unavailable"), as in the tested example above. Rebuild callers with matching development references and libraries.

The current operator set.

OperationOptionResult
Transform a valueMapMap
Chain an outcomeThenThen
Keep a matching valueFilter
Transform an errorMapError
Recover with an outcomeOrElse (None)OrElse (Error)
Extract with a fallbackUnwrapOr, UnwrapOrElseUnwrapOr, UnwrapOrElse
Handle both branchesMatchMatch
Run a branch actionTap, TapNoneTap, TapError
Convert to zero/one elementsToIterableToIterable
Convert absence to an errorOkOr (value or factory), MapResult, ThenResult
Remove one nested layerFlatten

Callbacks run immediately on the matching branch. Tap methods return the original outcome. UnwrapOr takes an already evaluated fallback; UnwrapOrElse calls a parameterless factory only for None or Error. Use Result.OrElse or Match if the error itself is needed. ToIterable allocates a small collection and discards an Error payload, so use patterns when the error needs handling.

Raven.Core users will recognize these operators: neoCLR calls Where Filter and ToEnumerable ToIterable. Throwing unwrap helpers, generic-default helpers, nullable adapters, JSON integration and context-error support are outside this slice. Collection queries have their own .NET operator mapping.

Behavior and limits.

Unlike exception-based APIs in .NET, recoverable outcomes are in the return type. This makes handling explicit and changes caller code. Runtime faults still exist; Result does not convert every failure into a recoverable case.

Detailed contract and comparisons →

PROPOSED DIRECTION

Where we’re heading.

Keep patterns and propagation consistent across the library. Broader async APIs may combine Task with Result, while cancellation and cleanup need their own contracts. No complete async model is implied by this working slice.

Related proposals and open questions →

What would you try?

Bring a small use case, the code you tried and the behavior you expected. Which part of this contract helps, and which part should change?

Share feedback on GitHub ↗