UnambitiousFx.Functional 1.0.6

dotnet add package UnambitiousFx.Functional --version 1.0.6
                    
NuGet\Install-Package UnambitiousFx.Functional -Version 1.0.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="UnambitiousFx.Functional" Version="1.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="UnambitiousFx.Functional" Version="1.0.6" />
                    
Directory.Packages.props
<PackageReference Include="UnambitiousFx.Functional" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add UnambitiousFx.Functional --version 1.0.6
                    
#r "nuget: UnambitiousFx.Functional, 1.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package UnambitiousFx.Functional@1.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=UnambitiousFx.Functional&version=1.0.6
                    
Install as a Cake Addin
#tool nuget:?package=UnambitiousFx.Functional&version=1.0.6
                    
Install as a Cake Tool

UnambitiousFx.Functional

Build Status NuGet NuGet Downloads codecov License: MIT .NET

A lightweight, modern functional programming library for .NET that makes error handling and optional values elegant and type-safe.

๐Ÿ”ง Compatibility & support

  • Dependency-free: No external runtime dependencies โ€” the library is dependency-less at runtime.
  • Supported .NET versions: Supports all Microsoft LTS releases and the latest non-LTS release. See the CI matrix or the Releases page for exact versions.

๐ŸŽฏ Features

  • Result<T> - Railway-oriented programming for error handling without exceptions
  • Maybe<T> - Type-safe optional values, no more null reference exceptions
  • OneOf<T1..T10> - Discriminated unions via source generators
  • Rich Error Types - ValidationError, NotFoundError, ConflictError, UnauthorizedError, and more
  • Comprehensive Extensions - Bind, Map, Match, Tap, Ensure, Recover, and dozens more
  • Async First - Full support for Task<T> and ValueTask<T>
  • ASP.NET Core Integration - Convert results to HTTP responses effortlessly
  • Metadata Support - Attach contextual information to results
  • xUnit Testing Utilities - Fluent assertions for functional types
  • Performance Focused - Zero allocation where possible, benchmarks included
  • Debugger Friendly - Enhanced debugging experience with custom visualizers

๐Ÿ“ฆ Installation

dotnet add package UnambitiousFx.Functional

For ASP.NET Core integration:

dotnet add package UnambitiousFx.Functional.AspNetCore

For xUnit testing utilities:

dotnet add package UnambitiousFx.Functional.xunit

๐Ÿš€ Quick Start

Result<T> - Error Handling Made Simple

Instead of throwing exceptions:

using UnambitiousFx.Functional;

// Traditional approach with exceptions
public User GetUser(int id)
{
    var user = _repository.Find(id);
    if (user is null)
        throw new NotFoundException($"User {id} not found");
    return user;
}

// Functional approach with Result<T>
public Result<User> GetUser(int id)
{
    var user = _repository.Find(id);
    return user is not null
        ? Result.Success(user)
        : Result.Failure<User>(new NotFoundError($"User {id} not found"));
}

Pattern Matching

var result = GetUser(42);

result.Match(
    success: user => Console.WriteLine($"Found: {user.Name}"),
    failure: error => Console.WriteLine($"Error: {error.Message}")
);

Chaining Operations (Railway-Oriented Programming)

public Result<Order> CreateOrder(int userId, OrderRequest request)
{
    return GetUser(userId)
        .Ensure(user => user.IsActive, new ValidationError("User is not active"))
        .Bind(user => ValidateOrder(request))
        .Bind(validOrder => SaveOrder(validOrder))
        .Tap(order => _eventBus.Publish(new OrderCreated(order.Id)))
        .WithMetadata("userId", userId);
}

Maybe<T> - Optional Values Without Null

public Maybe<User> FindUserByEmail(string email)
{
    var user = _repository.FindByEmail(email);
    return user is not null
        ? Maybe.Some(user)
        : Maybe.None<User>();
}

// Pattern matching
var maybeUser = FindUserByEmail("user@example.com");
maybeUser.Match(
    some: user => Console.WriteLine($"Found: {user.Name}"),
    none: () => Console.WriteLine("User not found")
);

// Convert to Result
var result = maybeUser.ToResult(new NotFoundError("User not found"));

OneOf<T> - Discriminated Unions

// Represent a value that can be one of several types
public OneOf<Success, ValidationError, NotFoundError> ProcessRequest(int id)
{
    if (id <= 0)
        return new ValidationError("Invalid ID");

    var item = _repository.Find(id);
    if (item is null)
        return new NotFoundError($"Item {id} not found");

    return new Success();
}

// Pattern match on all cases
var response = ProcessRequest(id);
response.Match(
    first: success => Ok(),
    second: validation => BadRequest(validation.Message),
    third: notFound => NotFound(notFound.Message)
);

๐Ÿ“š Core Concepts

Result<T> Operations

Transformation
// Map - Transform success value
Result<int> result = Result.Success(5);
Result<int> doubled = result.Map(x => x * 2); // Success(10)

// Bind - Chain operations that return Result
Result<User> userResult = GetUser(id);
Result<Order> orderResult = userResult.Bind(user => CreateOrder(user));

// Flatten - Unwrap nested results
Result<Result<int>> nested = Result.Success(Result.Success(42));
Result<int> flat = nested.Flatten(); // Success(42)
Error Handling
// Recover - Provide fallback on error
Result<User> result = GetUser(id)
    .Recover(error => GetDefaultUser());

// MapError - Transform error
Result<User> result = GetUser(id)
    .MapError(error => new CustomError(error.Message));

// Ensure - Add validation
Result<User> result = GetUser(id)
    .Ensure(user => user.Age >= 18, new ValidationError("Must be 18+"));
Side Effects
// Tap - Execute side effect on success
Result<Order> result = CreateOrder(request)
    .Tap(order => _logger.LogInformation($"Order {order.Id} created"));

// TapError - Execute side effect on failure
Result<Order> result = CreateOrder(request)
    .TapError(error => _logger.LogError($"Failed: {error.Message}"));

// TapBoth - Execute side effect regardless of result
Result<Order> result = CreateOrder(request)
    .TapBoth(
        success: order => _metrics.RecordSuccess(),
        failure: error => _metrics.RecordFailure()
    );
Value Access
// TryGet - Safe value extraction
if (result.TryGet(out var value, out var error))
{
    Console.WriteLine($"Success: {value}");
}
else
{
    Console.WriteLine($"Error: {error.Message}");
}

// ValueOr - Provide default value
var user = result.ValueOr(GetDefaultUser());

// ValueOrThrow - Get value or throw
var user = result.ValueOrThrow(); // Throws if failed

Error Types

// Basic error
var error = new Error("Something went wrong");

// Validation error with field details
var validationError = new ValidationError("Invalid input", new Dictionary<string, object?>
{
    ["Email"] = "Invalid email format",
    ["Age"] = "Must be at least 18"
});

// Not found error
var notFoundError = new NotFoundError("User not found");

// Conflict error
var conflictError = new ConflictError("Email already exists");

// Unauthorized error
var unauthorizedError = new UnauthorizedError("Access denied");

// Exceptional error (wrap exceptions)
try
{
    // risky operation
}
catch (Exception ex)
{
    return Result.Failure(new ExceptionalError(ex));
}

// Aggregate error (multiple errors)
var errors = new List<Error>
{
    new ValidationError("Invalid name"),
    new ValidationError("Invalid email")
};
var aggregateError = new AggregateError(errors);

Metadata

Attach contextual information to results:

var result = Result.Success(user)
    .WithMetadata("requestId", requestId)
    .WithMetadata("timestamp", DateTime.UtcNow)
    .WithMetadata("source", "UserService");

// Access metadata
if (result.Metadata.TryGetValue("requestId", out var requestId))
{
    _logger.LogInformation($"Request {requestId} completed");
}

// Fluent builder
var result = Result.Success(user)
    .WithMetadata(builder => builder
        .Add("requestId", requestId)
        .Add("duration", stopwatch.ElapsedMilliseconds)
        .Add("cacheHit", false));

Async Support

All operations work seamlessly with Task<T> and ValueTask<T>:

public async Task<Result<Order>> CreateOrderAsync(int userId, OrderRequest request)
{
    return await GetUserAsync(userId)
        .Bind(user => ValidateOrderAsync(request))
        .Bind(validOrder => SaveOrderAsync(validOrder))
        .Tap(order => PublishEventAsync(order));
}

// Works with ValueTask too
public async ValueTask<Result<User>> GetUserAsync(int id)
{
    var user = await _repository.FindAsync(id);
    return user is not null
        ? Result.Success(user)
        : Result.Failure<User>(new NotFoundError($"User {id} not found"));
}

๐ŸŒ ASP.NET Core Integration

using UnambitiousFx.Functional.AspNetCore;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // Automatically converts Result to appropriate HTTP response
    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id)
    {
        return await _userService
            .GetUserAsync(id)
            .ToHttpResult(); // 200 OK or 404 Not Found
    }

    [HttpPost]
    public async Task<IActionResult> CreateUser(CreateUserRequest request)
    {
        return await _userService
            .CreateUserAsync(request)
            .ToHttpResult(); // 200 OK, 400 Bad Request, or 409 Conflict
    }
}

Configure error mapping:

services.AddResultHttp(options =>
{
    options.MapError<ValidationError>(StatusCodes.Status400BadRequest);
    options.MapError<NotFoundError>(StatusCodes.Status404NotFound);
    options.MapError<ConflictError>(StatusCodes.Status409Conflict);
    options.MapError<UnauthorizedError>(StatusCodes.Status401Unauthorized);
});

๐Ÿงช Testing with xUnit

using UnambitiousFx.Functional.xunit;

[Fact]
public void CreateUser_WithValidData_ReturnsSuccess()
{
    // Arrange
    var request = new CreateUserRequest { Name = "John", Email = "john@example.com" };

    // Act
    var result = _service.CreateUser(request);

    // Assert
    result.Should().BeSuccess()
        .Which(user => user.Name.Should().Be("John"));
}

[Fact]
public void CreateUser_WithInvalidEmail_ReturnsValidationError()
{
    // Arrange
    var request = new CreateUserRequest { Name = "John", Email = "invalid" };

    // Act
    var result = _service.CreateUser(request);

    // Assert
    result.Should().BeFailure()
        .WithError<ValidationError>()
        .Which(error => error.Message.Should().Contain("email"));
}

[Fact]
public void FindUser_WhenNotExists_ReturnsNone()
{
    // Act
    var maybe = _repository.FindByEmail("notfound@example.com");

    // Assert
    maybe.Should().BeNone();
}

๐ŸŽ“ Philosophy

This library embraces:

  • Railway-Oriented Programming - Operations form a "railway track" where success continues down the happy path and errors short-circuit to the failure path
  • Explicit Error Handling - Errors are values, not exceptions. They're part of your type signatures
  • Composition Over Conditionals - Chain operations naturally using Bind, Map, and other combinators
  • Type Safety - Compiler enforces error handling, reducing runtime surprises
  • Functional Core, Imperative Shell - Pure functional core with pragmatic integration for .NET ecosystem

๐Ÿ“Š Performance

We take performance seriously. Check our benchmarks comparing against:

  • Traditional exception-based code
  • FluentResults
  • Ardalis.Result

Key highlights:

  • Zero allocations for success paths in many scenarios
  • readonly struct value types minimize heap pressure
  • Lazy error aggregation
  • Optimized async state machines

๐Ÿค Contributing

We welcome contributions! Please read our Contributing Guide for details on:

  • Code of conduct
  • Development setup
  • Coding standards
  • Testing requirements
  • Pull request process

Check out our good first issues to get started!

๐Ÿ“ Release notes

See the Releases page on GitHub for detailed release notes and version history: https://github.com/UnambitiousFx/Functional/releases

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments

Inspired by:

๐Ÿ“š Further Reading


Made with โค๏ธ by the UnambitiousFx team

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on UnambitiousFx.Functional:

Package Downloads
UnambitiousFx.Functional.AspNetCore

A lightweight functional programming library for .NET providing Result, Maybe, and OneOf types for railway-oriented programming and error handling.

UnambitiousFx.Functional.xunit

A lightweight functional programming library for .NET providing Result, Maybe, and OneOf types for railway-oriented programming and error handling.

UnambitiousFx.Synapse.Abstractions

A lightweight, performance-oriented library for building message-driven applications and in-process mediators. Provides composable primitives for Commands, Queries, Events, and Pipelines with a focus on low latency and minimal allocations.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.6 146 1/18/2026
1.0.5 132 1/16/2026
1.0.4 128 1/12/2026
1.0.3 138 1/9/2026
1.0.2 129 1/7/2026
1.0.1 132 1/6/2026
1.0.0 218 1/3/2026