UnambitiousFx.Functional

website·Indexed 31 days ago

https://functional.unambitiousfx.com

A lightweight, performance-focused functional programming library for .NET. It provides functional types such as Result and Result<TValue> for error handling as values instead of exceptions, and Maybe<T> for modeling optional values. The ecosystem includes UnambitiousFx.Functional.AspNetCore (v2.0.0) for ASP.NET Core integration with Minimal API and MVC patterns, and UnambitiousFx.Functional.xunit for fluent assertions of functional types in tests.

Tokens
16.4K
Snippets
97
Records
106
Agent score
51%

What's inside UnambitiousFx.Functional

  1. Overview of UnambitiousFx.Functional

    UnambitiousFx.Functional is a performance-focused functional programming library for .NET designed to replace control-flow exceptions with type-safe failure handling using railway-oriented programming. Key features include Result<T> for failure handling, Maybe<T> for optional values, rich machine-readable failure types (e.g., ValidationFailure, NotFoundFailure), and full support for Task<T> and ValueTask<T>. The library is built using readonly struct value types to ensure zero-allocation on the success path and is compatible with NativeAOT.
  2. Use Result and Result<T> for error handling

    UnambitiousFx.Functional uses Result (for success/failure) and Result<T> (for success-with-value/failure) to treat errors as values. This approach, known as railway-oriented programming, ensures that operations are composed into a pipeline that short-circuits upon the first failure, bypassing subsequent steps on the 'success track'. This is preferred over exceptions for expected domain outcomes, while exceptions should be reserved for genuine programming errors like bugs or misconfiguration.
  3. Use Maybe<T> for optional values

    Maybe<T> models an optional value: it is either Some (a present value of type T) or None (the explicit absence of a value). It is a readonly record struct to avoid heap allocation. Use Maybe<T> for cache misses, lookups, or optional configurations. If the absence of a value is considered an error that requires a reason, use Result<T> instead.
  4. Handle asynchronous Result chains with Task and ValueTask overloads

    Most Result<T> operators provide extension overloads for Task<Result<T>> and ValueTask<Result<T>>. This allows developers to mix synchronous and asynchronous continuations within a single awaited chain. Supported operators include Map, Bind, Then, Flatten, Match, Switch, Ensure, Recover, Compensate, Try, Tap, TapFailure, ValueOr*, Combine, and LINQ operators.
    Result<Receipt> receipt = await LoadCartAsync(cartId)
        .Bind(cart => ReserveStockAsync(cart)) // Async continuation
        .Map(reservation => new Receipt(reservation.Id)); // Sync continuation
  5. Inspect and pattern match Result values

    Result and Result<T> do not expose public Value or Error properties to force explicit handling of both success and failure cases. Use Match to collapse results into a single value or Switch for side-effecting actions.
    // Boolean guards and TryGet
    if (result.IsSuccess) { /* ... */ }
    if (result.TryGet(out var user, out var error)) { /* ... */ }
    
    // Collapsing to a value
    string message = result.Match(
        success: user  => $"Welcome, {user.Name}",
        failure: error => $"Failed: {error.Message}");
    
    // Side-effecting actions
    result.Switch(
        success: user  => Console.WriteLine(user.Name),
        failure: error => Console.WriteLine(error.Message));
    
    // Pattern matching on failure types
    return result.Match(
        success: user  => Results.Ok(user),
        failure: error => error switch
        {
            NotFoundFailure     => Results.NotFound(error.Message),
            ValidationFailure   => Results.BadRequest(error.Message),
            UnauthorizedFailure => Results.Forbid(),
            _                   => Results.Problem(error.Message)
        });
  6. Integrate UnambitiousFx documentation into AI prompts

    To ground an AI assistant in the real UnambitiousFx API, use one of the following methods:

    • URL Reference: Paste the /llms.txt or /llms-full.txt URL and instruct the assistant to read the documentation before answering.
    • Automatic Fetching: Use assistants or editors that natively support the llms.txt convention to automatically discover and load the bundles.
    • Manual Context Attachment: Download the /llms-full.txt file and attach it directly to your project or prompt context.
  7. Extract values from Maybe<T> with Match, ValueOr, and OrElse

    To exit the Maybe context:

    • Match: Collapses both Some and None branches into a single value.
    • Switch: Executes a side effect for each branch.
    • ValueOr: Unwraps to T, providing a fallback value or factory for the None case.
    • OrElse: Provides an alternative Maybe<T> if the current one is None.
    // Match
    string label = maybe.Match(
        some: value => $"Found {value}",
        none: () => "Nothing here");
    
    // ValueOr
    int retries = Maybe.None<int>().ValueOr(3);
    int lazy = Maybe.None<int>().ValueOr(() => Compute());
    
    // OrElse
    Maybe<User> user = primaryLookup
        .OrElse(secondaryLookup)
        .OrElse(() => FallbackLookup());
  8. Transform Maybe<T> using Map, Bind, and Filter

    Use these operators to chain logic without explicit null checks:

    • Map: Transforms the inner value while preserving None.
    • Bind: Transforms the value using a function that returns another Maybe<T>, flattening the result to avoid Maybe<Maybe<T>>.
    • Filter: Keeps the value if a predicate is true; otherwise, it returns None.
    Maybe<string> name = Maybe.Some(new User("Ada"))
        .Filter(user => user.IsActive)        // Some(user) or None
        .Map(user => user.Name)               // Some("Ada") or None
        .Bind(LookupDisplayName);             // flattens Maybe<string>
    
    Maybe<string> LookupDisplayName(string name) =>
        name.Length > 0 ? Maybe.Some(name.ToUpperInvariant()) : Maybe.None<string>();
  9. Handle failures and recover Result states

    Use Ensure to trigger a failure based on a predicate, Recover to provide a fallback value, and Try to wrap exception-throwing code into a Result failure.
    // Fail if predicate is false
    var checkedResult = result.Ensure(val => val > 0, val => new Failure("Must be positive"));
    
    // Recover from failure with a fallback
    var safeResult = result.Recover(fallbackValue);
    
    // Wrap throwing code
    var tryResult = result.Try(val => someThrowingMethod(val));
  10. Convert Maybe<T> to Result<T>

    Use ToResult to convert a Maybe<T> into a Result<T> when an empty value should be treated as a failure. You can provide a specific Failure object, a lazy factory, or a simple error message string.
    using UnambitiousFx.Functional.Failures;
    
    Result<User> required = FindUser(id)
        .ToResult(new NotFoundFailure("User", id.ToString()));
    
    Result<User> lazyFailure = FindUser(id)
        .ToResult(() => new NotFoundFailure("User", id.ToString()));
    
    Result<User> messageFailure = FindUser(id)
        .ToResult("User not found");
  11. Use llms.txt bundles for AI-assisted development with UnambitiousFx

    UnambitiousFx provides machine-readable documentation bundles following the llmstxt.org standard to ensure AI assistants use the actual v2.0.0 API (including Result, Result<T>, Maybe<T>, and UnambitiousFx.Functional.Failures) rather than training-data approximations.

    There are two available bundles:

    1. /llms.txt: An index of every documentation section with links. Use this for day-to-day questions to keep the AI's context window small while allowing it to discover and pull specific pages.
    2. /llms-full.txt: The entire documentation set concatenated into one file. Use this when you need the complete API surface, such as during large codebase migrations or when generating new pipelines from scratch.