ARPL 1.0.10

dotnet add package ARPL --version 1.0.10
                    
NuGet\Install-Package ARPL -Version 1.0.10
                    
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="ARPL" Version="1.0.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ARPL" Version="1.0.10" />
                    
Directory.Packages.props
<PackageReference Include="ARPL" />
                    
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 ARPL --version 1.0.10
                    
#r "nuget: ARPL, 1.0.10"
                    
#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.
#addin nuget:?package=ARPL&version=1.0.10
                    
Install ARPL as a Cake Addin
#tool nuget:?package=ARPL&version=1.0.10
                    
Install ARPL as a Cake Tool

<div align="center">

<img src=".github/img/logo.png" alt="drawing" width="700px"/></br>

</div>

NuGet Downloads Build codecov License

If you find ARPL helpful, please give it a star โญ! It helps the project grow and improve.

ARPL

A lightweight C# library providing robust discriminated unions for error handling and functional programming patterns. ARPL offers two main types: Either<L,R> for generic discriminated unions and SResult<R> for specialized success/error handling.

Why ARPL? ๐Ÿค”

  • โœจ Type-safe error handling without exceptions
  • ๐Ÿ”„ Rich functional methods for composing operations
  • ๐ŸŽฏ Explicit error cases in method signatures
  • ๐Ÿ“ฆ Collection of errors support out of the box
  • ๐Ÿ”— Chainable operations with fluent API
  • ๐Ÿงช Testable code with predictable flows

Table of Contents ๐Ÿ“‘

The Result Pattern ๐ŸŽฏ

The Result Pattern is an elegant alternative to traditional exception handling that makes error cases explicit in your code. Instead of throwing exceptions that can be caught anywhere in the call stack, methods return a Result type that can represent either success or failure.

Traditional Exception Handling

public User CreateUser(string email, string password)
{
    try
    {
        // Validate email
        if (string.IsNullOrEmpty(email))
            throw new ValidationException("Email is required");
            
        if (!IsValidEmail(email))
            throw new ValidationException("Invalid email format");

        // Validate password
        if (string.IsNullOrEmpty(password))
            throw new ValidationException("Password is required");

        // Create and save user
        var user = new User(email, password);
        await _repository.Save(user);

        return user;
    }
    catch (ValidationException ex)
    {
        _logger.LogWarning(ex, "Validation failed when creating user");
        throw; 
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error creating user");
        throw;
    }
}

Using ARPL's Result Pattern

public SResult<User> CreateUser(string email, string password)
{
    // Validate inputs
    if (string.IsNullOrEmpty(email))
        return Fail<User>(Errors.New("Email is required"));

    if (string.IsNullOrEmpty(password))
        return Fail<User>(Errors.New("Password is required"));
        
    if (!IsValidEmail(email))
        return Fail<User>(Errors.New("Invalid email format"));

    // Create and save user
    try
    {
        var user = new User(email, password);
        _repository.Save(user);
 
        return Success(user);
    }
    catch (Exception ex)
    {
        return Fail<User>(Errors.New(ex, "Failed to create user")); //Unexpected Error
    }
}

Benefits of the Result Pattern

  1. Explicit Error Handling: Error cases are part of the method signature, making it clear that a method can fail and forcing error handling at compile time.

  2. Type Safety: The compiler ensures you handle both success and error cases through pattern matching, preventing runtime surprises.

  3. No Exceptions: Better performance by avoiding the overhead of exception handling and stack traces. Exceptions are reserved for truly exceptional cases.

  4. Composable: Easy to chain operations using functional methods like Map and Bind, making the code more readable and maintainable.

  5. Rich Error Types: Built-in support for different error types and error collections, allowing for more granular error handling.

  6. Predictable Flow: All possible outcomes are clearly defined and handled in a structured way, making the code easier to reason about.

  7. Better Testing: Easier to test error cases since they're explicit in the type system rather than relying on exception handling.

Features ๐Ÿš€

  • Either<L,R> - A generic discriminated union that can hold one of two possible types
  • SResult<R> - A specialized result type for handling success/error scenarios
  • Implicit conversions between Either<Error,R> and SResult<R>
  • Pattern matching support for elegant value handling
  • Type-safe error handling without exceptions
  • Functional programming friendly design

Getting Started ๐Ÿƒ

Installation

Install via NuGet:

Install-Package ARPL

Basic Usage

Either<L,R>

A generic discriminated union that can hold one of two possible types:

// Create Either instances
var right = Either<string, int>.Right(42);        // Success path
var left = Either<string, int>.Left("error");     // Error path

// Check which value is present
if (right.IsRight)
    Console.WriteLine($"Value: {right.RightValue}"); // 42

if (left.IsLeft)
    Console.WriteLine($"Value: {left.LeftValue}"); // error

// Pattern match to handle both cases
var message = left.Match(
    left => $"Error: {left}",
    right => $"Value: {right}");
SResult<R>

A specialized result type for success/error scenarios:

// Create results
var success = SResult<int>.Success(42);                     // Success case
var error = SResult<int>.Fail(Errors.New("Invalid input")); // Error case

// Check result type
if (success.IsSuccess)
    Console.WriteLine($"Value: {success.SuccessValue}");
else
    Console.WriteLine($"Error: {success.ErrorValue.Message}");

// Pattern match for handling
var message = error.Match(
    error => $"Failed: {error.Message}",
    value => $"Success: {value}");
Error

A rich error type with support for messages, codes, and chaining:

// Create errors
var simple = Errors.New("Something went wrong");
var coded = Errors.New("Invalid input", "INVALID_INPUT");
var chained = simple + coded;

// Access error details
Console.WriteLine(simple.Message);     // "Something went wrong"
Console.WriteLine(coded.Code);        // "INVALID_INPUT"
Console.WriteLine(chained.Message)   // ["Something went wrong","Invalid input"] 

Error Handling

ARPL provides a flexible error handling system that allows you to work with both single errors and collections of errors. The Error class serves as the base for all error types, and the ErrorCollection allows you to aggregate multiple errors together.

Single Errors

// Create a simple error
var error = Errors.New("Invalid input", "ERR001");

// Create an unexpected error from an exception
var unexpectedError = Errors.New(new Exception("Database connection failed"));

// Check error types
if (error.HasErrorOf<ExpectedError>())
    Console.WriteLine("This is an expected error");

// Check exception types
if (unexpectedError.HasExceptionOf<DbException>())
    Console.WriteLine("This is a database error");

Multiple Errors

When you need to collect and combine multiple errors, use ErrorCollection:

// Start with an empty error collection
var errors = Errors.EmptyError();

// Add errors as they are found
errors.Add(Errors.New("Invalid email", "VAL001"));
errors.Add(Errors.New("Password too short", "VAL002"));

// You can also combine errors using the + operator
var error1 = Errors.New("Field required", "VAL003");
var error2 = Errors.New("Invalid format", "VAL004");
var combined = error1 + error2; // Implicit creates a new ErrorCollection
combined += Errors.New("Missing argument", "VAL005");

// Enumerate through errors
foreach (var error in combined.AsEnumerable())
{
    Console.WriteLine($"{error.Code}: {error.Message}");
}

// Get error count
Console.WriteLine($"Total errors: {combined.Count}"); // 3

// Check if collection has specific error
var hasValidationError = combined.AsEnumerable().Any(e => e.Code.StartsWith("VAL"));

// Use in result types
return SResult<User>.Error(errors); // Works with both single Error and ErrorCollection

Bespoke Errors

ARPL allows you to create custom error types by extending the Error class. This enables you to create domain-specific errors that carry meaningful context for your application:

public record NotFoundError : Error
{
    public NotFoundError(string entityType, string identifier)
    {
        EntityType = entityType;
        Identifier = identifier;
    }

    public string EntityType { get; }
    public string Identifier { get; }
    public override string Message => $"{EntityType} with id {Identifier} was not found";
    public override bool IsExpected => true;
}

// Usage example:
public async Task<SResult<User>> GetUserById(string userId)
{
    var user = await _repository.FindUserById(userId);
    if (user == null)
        return SResult<User>.Error(new NotFoundError("User", userId));

    return SResult<User>.Success(user);
}

// Pattern matching with custom error
var result = await GetUserById("123");
var message = result.Match(
    fail => fail is NotFoundError nf 
        ? $"Could not find {nf.EntityType} {nf.Identifier}" 
        : "Unknown error",
    success => $"Found user: {success.Name}"
);

Bespoke errors provide several benefits:

  1. Type-safe error handling with pattern matching
  2. Rich error context specific to your domain
  3. Clear distinction between expected and unexpected errors
  4. Consistent error handling across your application

Implicit Conversions

ARPL supports implicit conversions between Either<Error,R> and SResult<R>, making it seamless to work with both types:

// Convert from Either to SResult
Either<Error, int> either = Either<Error, int>.Right(42);
SResult<int> result = either; // Implicit conversion

// Convert from SResult to Either
SResult<int> sresult = SResult<int>.Success(42);
Either<Error, int> converted = sresult; // Implicit conversion

Note: The implicit conversion only works for Either<Error, R> and SResult<R>. Attempting to convert other types will throw an exception.

StaticFactory Helpers

For a more functional and concise style, ARPL provides the StaticFactory class, which offers utility methods to create instances of SResult and Either in a direct and expressive way:

using static Arpl.Core.StaticFactory;

// Create a success result
var success = Success(42); // SResult<int>

// Create a failure result
var fail = Fail<int>(new Error("fail")); // SResult<int>

// Create an Either with a left value
var left = Left<string, int>("error"); // Either<string, int>

// Create an Either with a right value
var right = Right<string, int>(42); // Either<string, int>

// Try example
var result = Try(() => Success(int.Parse(input))); // SResult<int>

Available factory methods:

  1. Success<T>(T value): Creates a successful SResult<T>
  2. Fail<T>(Error value): Creates a failed SResult<T>
  3. Left<L,R>(L value): Creates an Either<L,R> with left value
  4. Right<L,R>(R value): Creates an Either<L,R> with right value
  5. Try<R>(Func<SResult<R>>): Safely executes a function that returns SResult<R>
  6. TryAsync<R>(Func<Task<SResult<R>>>): Safely executes an async function that returns Task<SResult<R>>

Type Features

Either<L,R>

  • Left(L value) - Creates a new Either instance containing a left value
  • Right(R value) - Creates a new Either instance containing a right value
  • IsLeft - Indicates if the instance contains a left value
  • IsRight - Indicates if the instance contains a right value
  • LeftValue - Gets the left value (if present)
  • RightValue - Gets the right value (if present)
  • Match - Pattern matching for transforming or handling the contained value
  • MatchAsync - Asynchronous pattern matching for handling the contained value
  • Map - Transforms the right value using a mapping function (if present)
  • MapAsync - Transforms the right value using an async mapping function (if present)
  • Bind - Chains operations that return Either (monadic bind)
  • BindAsync - Asynchronously chains operations that return Either
  • Apply - Transforms both left and right values into a new Either
  • ApplyAsync - Asynchronously transforms both left and right values into a new Either
  • Sequence - Transforms a collection of Either into an Either of collection
  • SequenceAsync - Asynchronously transforms a collection of Either into an Either of collection
  • Traverse - Maps and sequences in one step
  • TraverseAsync - Asynchronously maps and sequences in one step

SResult<R>

  • Success(R value) - Creates a new success result
  • Error(Error value) - Creates a new error result
  • IsSuccess - Indicates if the result represents success
  • IsFail - Indicates if the result represents an error
  • SuccessValue - Gets the success value
  • ErrorValue - Gets the error value
  • Match - Pattern matching for transforming or handling the result
  • MatchAsync - Asynchronous pattern matching for handling the result
  • AsEither - Converts the SResult<R> to Either<Error, R>
  • Map - Transforms the success value using a mapping function (if present)
  • MapAsync - Transforms the success value using an async mapping function (if present)
  • Bind - Chains operations that return SResult (monadic bind)
  • BindAsync - Asynchronously chains operations that return SResult
  • Apply - Transforms both error and success values into a new SResult
  • ApplyAsync - Asynchronously transforms both error and success values into a new SResult
  • Try - Executes a function safely, catching any exceptions into an error result
  • TryAsync - Executes an async function safely, catching any exceptions into an error result
  • Sequence - Transforms a collection of SResult into an SResult of collection
  • SequenceAsync - Asynchronously transforms a collection of SResult into an SResult of collection
  • Traverse - Maps and sequences in one step
  • TraverseAsync - Asynchronously maps and sequences in one step

Error

  • New(string message) - Creates a new expected error with a message
  • New(string message, string code) - Creates a new expected error with a message and code
  • New(Exception ex) - Creates a new unexpected error from an exception
  • New(Exception ex, string message, string code) - Creates a new unexpected error with a message and a code
  • Message - Gets the error message
  • Code - Gets the error code (if present)
  • Exception - Gets the exception (if present)
  • IsExpected - Indicates if the error was expected
  • HasErrorOf<T>() - Checks if the error is of type T
  • HasExceptionOf<T>() - Checks if the error's exception is of type T

Functional Methods ๐Ÿงฎ

ARPL provides a rich set of functional methods to compose and transform values:

Map & MapAsync

Transform the success/right value while preserving the context:

// Map a successful value
SResult<int> result = SResult<int>.Success(42);
var doubled = result.Map(x => x * 2); // Success(84)

// Map with Either
Either<Error, int> either = Either<Error, int>.Right(42);
var doubled = either.Map(x => x * 2); // Right(84)

// Async mapping
var asyncResult = await result.MapAsync(async x => {
    await Task.Delay(100); // Simulate async work
    return x * 2;
});

Bind & BindAsync

Chain operations that might fail:

// Simple validation chain
SResult<int> Parse(string input) =>
    int.TryParse(input, out var number)
        ? Success(number)
        : Fail<int>(Errors.New("Invalid number"));

SResult<int> Validate(int number) =>
    number > 0
        ? Success(number)
        : Fail<int>(Errors.New("Number must be positive"));

// Chain operations with Bind
var result = Parse("42")
    .Bind(Validate)
    .Map(x => x * 2); // Success(84)

Match & MatchAsync

Pattern match to handle both success and error cases:

// Handle validation result
var result = ValidateAge(age);
var message = result.Match(
    error => $"Invalid age: {error.Message}",
    age => $"Age {age} is valid");

// Format API response
var apiResult = await GetUserAsync(id);
var response = apiResult.Match(
    error => new ErrorResponse { Code = error.Code, Message = error.Message },
    user => new UserResponse { Id = user.Id, Name = user.Name });

// With Either for custom error handling
var parseResult = TryParseJson<UserData>(json);
var data = parseResult.Match(
    error => new UserData { IsValid = false, Error = error },
    success => success with { IsValid = true });

Sequence & SequenceAsync

Transform a collection of results into a result of collection:

// Sequence a list of results
var results = new[] {
    SResult<int>.Success(1),
    SResult<int>.Success(2),
    SResult<int>.Success(3)
};
var combined = results.Sequence(); // Success([1,2,3])

// If any fails, the whole operation fails
var mixed = new[] {
    SResult<int>.Success(1),
    SResult<int>.Fail(Errors.New("Oops")),
    SResult<int>.Success(3)
};
var failed = mixed.Sequence(); // Fail("Oops")

Traverse & TraverseAsync

Map and sequence in one step:

// Parse a list of strings into numbers
var strings = new[] { "1", "2", "3" };
var numbers = strings.Traverse(str => 
    int.TryParse(str, out var num)
        ? Success(num)
        : Fail<int>(Errors.New($"Invalid number: {str}")));

// Async traversal
var urls = new[] { "url1", "url2" };
var contents = await urls.TraverseAsync(async url => {
    try {
        var content = await httpClient.GetStringAsync(url);
        return Success(content);
    }
    catch (Exception ex) {
        return Fail<string>(Errors.New(ex, $"Failed to fetch {url}"));
    }
});

Try & TryAsync

Start functional chains with exception-safe operations:

// Start a chain with Try for sync operations
var result = Try(() => int.Parse(input))        // Returns SResult<int>
    .Map(x => x * 2)                           // Transform if successful
    .Bind(x => Validate(x));                   // Chain with another operation

// Complex validation chain starting with Try
var userResult = Try(() => {
    if (string.IsNullOrEmpty(email))
        return Fail<User>(Errors.New("Email required"));
    return Success(new User(email));
})
.Bind(ValidateUser)                            // Chain with other validations
.BindAsync(SaveUserAsync);                     // Continue with async operations

// Start async chains with TryAsync
var apiResult = await TryAsync(async () => {
    var response = await httpClient.GetAsync(url);
    return response.IsSuccessStatusCode
        ? Success(await response.Content.ReadAsStringAsync())
        : Fail<string>(Errors.New($"API error: {response.StatusCode}"));
})
.Map(json => JsonSerializer.Deserialize<User>(json))  // Transform the result
.BindAsync(ValidateUserAsync);                       // Continue the chain

Apply & ApplyAsync

Transform both success and error cases:

// Convert errors to user-friendly messages
var result = SResult<int>.Fail(Errors.New("INVALID_INPUT"));
var friendly = result.Apply(
    error => SResult<string>.Success($"Please try again: {error.Message}"),
    value => SResult<string>.Success($"Your number is {value}"));

// With Either for custom error handling
var either = Either<int, string>.Left(404);
var handled = either.Apply(
    status => Either<string, string>.Right($"Error {status}"),
    content => Either<string, string>.Right($"Content: {content}"));

Do & DoAsync

The Do and DoAsync methods allow you to perform actions on the monad while ensuring it remains a monad. Unlike Map, Do requires you to return the same monad type. This is particularly useful when you want to perform actions that should happen regardless of success or error state.

// Using Do with Either - logging regardless of state
var result = Either<Error, int>.Right(42)
    .Do(either => {
        var state = either.IsRight ? "success" : "error";
        _metrics.TrackOperation($"process_number_{state}");
        return either;
    })
    .Map(x => x * 2);

// Using DoAsync with SResult - updating cache regardless of result
var asyncResult = await GetUserAsync(id)
    .DoAsync(async result => {
        // Update cache even if the operation failed
        await _cache.SetAsync($"user_{id}_last_access", DateTime.UtcNow);
        return result;
    })
    .Map(user => user.Name);

Transform & TransformAsync

The Transform and TransformAsync methods are powerful tools that allow you to access both success and error values of a monad and transform them into any type. Unlike Map and Bind which only work with the success value, Transform gives you access to the entire monad.

// Using Transform with Either
var either = Either<string, int>.Right(42);
var message = either.Transform(e => 
    e.IsRight ? $"Got number: {e.RightValue}" : $"Got error: {e.LeftValue}");

// Using TransformAsync with SResult
var result = await GetOrderAsync(id)
    .TransformAsync(async order => {
        if (order.IsSuccess)
        {
            var details = await _detailsService.GetDetailsAsync(order.SuccessValue);
            return new OrderViewModel(order.SuccessValue, details);
        }
        return new OrderViewModel(error: order.ErrorValue.Message);
    });

// Transform is especially useful for final transformations in a chain
var displayText = await ProcessDataAsync()
    .Map(data => data.Value)
    .Bind(ValidateData)
    .Transform(result => result.IsSuccess
        ? $"Success: {result.SuccessValue}"
        : $"Failed: {result.ErrorValue.Message}");

๐Ÿ•ท๏ธ As Uncle Ben said: "With great power comes great responsibility." Transform gives you complete access to the monad's object, breaking away from the usual functional constraints of Map and Bind. Use it wisely, preferably at the end of your chains when you need to make final transformations or present data to external systems.

Mixing Sync and Async Methods

ARPL provides seamless integration between synchronous and asynchronous operations in your functional chains.

// Start with async operation
var result = await GetUserAsync(id)        // Returns Task<SResult<User>>
    .Map(user => user.Name)               // Sync op, but returns Task<SResult<string>>
    .BindAsync(ValidateNameAsync)         // Async op
    .Map(name => name.ToUpper());         // Sync op, but returns Task<SResult<string>>

// Start with sync operation
var syncResult = Success("test")           // Returns SResult<string>
    .Map(str => str.ToUpper())           // Still sync, returns SResult<string>
    .BindAsync(ValidateAsync)            // Now async! Returns Task<SResult<string>>
    .Map(str => str.Length);             // Sync op, but returns Task<SResult<int>>

Note: Once your chain includes an async operation (like BindAsync or MapAsync), all subsequent operations become awaitable. This means they will return Task<T>, even if the operations themselves are synchronous. ARPL handles this transition automatically, allowing you to write clean code without worrying about async/sync conversions.

Best Practices

  1. Use Either<L,R> when you need a generic discriminated union
  2. Use SResult<R> for specific success/error handling scenarios
  3. Leverage pattern matching with Match for clean and safe value handling
  4. Prefer using the type system for error handling instead of exceptions

Anti-Patterns to Avoid

  1. โŒ Don't mix exceptions with Results
// Bad
public SResult<User> GetUser(int id)
{
    if (id <= 0)
        throw new ArgumentException("Invalid id"); // Don't throw!

    var user = _repository.GetById(id);
    return user == null
        ? Fail<User>(Errors.New("User not found"))
        : Success(user);
}

// Good
public SResult<User> GetUser(int id)
{
    if (id <= 0)
        return Fail<User>(Errors.New("Invalid id"));

    var user = _repository.GetById(id);
    return user == null
        ? Fail<User>(Errors.New("User not found"))
        : Success(user);
}
  1. โŒ Don't ignore the Result value
// Bad
await CreateUser(request); // Result ignored!

// Good
var result = await CreateUser(request);
if (result.IsFail)
    _logger.LogError("Failed to create user: {Errors}", result.ErrorValue);
  1. โŒ Don't use Result for expected flow control
// Bad - using Result for normal flow
public SResult<decimal> GetDiscount(User user)
{
    return user.IsPremium
        ? Success(0.1m)
        : Success(0m);
}

// Good - use normal return
public decimal GetDiscount(User user)
{
    return user.IsPremium ? 0.1m : 0m;
}

Demo Application

The repository includes a sample Web API project that demonstrates how to use ARPL in a real-world scenario. The demo implements a simple Person management API with proper error handling and functional programming patterns.

Features

  • CRUD operations for Person entity
  • Validation using Either<ValidateError, T>
  • Error handling with SResult<T>
  • HTTP response handling with custom HttpResult

Running the Demo

  1. Navigate to the sample directory:
cd sample/SampleWebApi
  1. Run the application:
dotnet run
  1. Open your browser at:
  • API: http://localhost:5297
  • Swagger UI: http://localhost:5297/swagger

API Endpoints

  • GET /api/person - List all persons
  • GET /api/person/{id} - Get person by id
  • POST /api/person - Create new person

Example Request

curl -X POST http://localhost:5297/api/person \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe","age":30}'

Benchmarking

Feature ARPL FluentResults OneOf ErrorOr
Generic Discriminated Union โœ… Either<L,R> โŒ โœ… โŒ
Result Type โœ… SResult<R> โœ… โŒ โœ…
Multiple Errors โœ… โœ… โŒ โœ…
Functional Methods โœ… โœ… โŒ โœ…
Async Support โœ… โœ… โŒ โœ…
Pattern Matching โœ… โœ… โœ… โœ…
Implicit Conversions โœ… โœ… โŒ โœ…
No Dependencies โœ… โœ… โœ… โœ…

ARPL combines the best of worlds:

  • Generic discriminated unions like OneOf
  • Rich error handling like FluentResults/ErrorOr
  • Full functional programming support
  • Seamless async/await integration

Contributing ๐Ÿค

Contributions are welcome! Feel free to submit issues and pull requests.

License ๐Ÿ“„

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


Disclaimer: This README was generated by Windsurf AI.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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 was computed.  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.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ARPL:

Package Downloads
ARPL.AspNetCore

A lightweight C# library providing robust discriminated unions for error handling and functional programming patterns. ARPL offers two main types: Either<L,R> for generic discriminated unions and SResult<R> for specialized success/error handling.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.10 91 5/2/2025
1.0.9 136 5/1/2025
1.0.8 141 4/30/2025
1.0.7 135 4/30/2025
1.0.6 145 4/29/2025
1.0.5 74 4/26/2025
1.0.5-rc 138 4/25/2025
1.0.4 141 4/25/2025
1.0.3 150 4/24/2025
1.0.2 93 4/19/2025
1.0.1 190 4/16/2025
1.0.0 193 4/15/2025