A functional programming library for C# that provides Option and Result types, discriminated unions, and functional combinators with comprehensive async support.
dotnet add package FunctionJunctionRepresents a value that may or may not exist.
// Implicitly created
Option<int> implicitSome = 10;
Option<int> implicitNone = default;
// Explicitly created
var explicitSome = Option.Some("Foo");
var explicitNone = Option.None<string>();
// From nullable value types
double? definitelyForSureNotNull = null;
var valueOption = Option.FromNullable(definitelyForSureNotNull);
// Nullable reference types can be converted implicitly
string? nullableReference = "Not null";
Option<string> referenceOption = nullableReference;Represents an operation that can succeed with TOk or fail with TError.
// Create results
Result<int, string> success = 42; // Implicit conversion
Result<int, string> failure = "Error occurred";
// Transform and validate
var result = ParseInt(userInput)
.Map(x => x * 2)
.Validate(x => x > 0, _ => "Value must be positive")
.MapError(error => $"Validation failed: {error}");
// Error recovery
var recovered = result
.Recover(TryAlternativeMethod)
.UnwrapOr(error => DefaultValue);Create sum types with automatic pattern matching via source generation.
[DiscriminatedUnion]
public partial record PaymentResult
{
public record Success(string TransactionId, decimal Amount) : PaymentResult;
public record Declined(string Reason) : PaymentResult;
public record Error(Exception Exception) : PaymentResult;
}
// Generated Match method
var message = paymentResult.Match(
onSuccess: (id, amount) => $"Payment ${amount} succeeded: {id}",
onDeclined: reason => $"Payment declined: {reason}",
onError: ex => $"Payment failed: {ex.Message}"
);All operations have async counterparts that work with Task-returning functions with Await prefix:
// Async operations
var userData = await userIdOption
.FlatMap(FetchUserAsync)
.AwaitFilter(user => user.IsActive)
.AwaitMap(EnrichUserData)
.AwaitUnwrapOr(GetDefaultUser);
// Combining multiple async operations
var result = await Result.All(
() => ValidateEmail(email),
() => CheckUserExists(email),
() => VerifyNotBlacklisted(email)
);
// Async enumerable extensions
await productsIds
.ToAsyncEnumerable()
.SelectWhere(async id => await TryLoadProduct(id))
.Scan(0m, (total, product) => total + product.Price)
.Last();Map<TResult>(Func<T, TResult>)- Transform the value if presentFlatMap<TResult>(Func<T, Option<TResult>>)- Chain operations that return OptionsFilter(Func<T, bool>)- Keep value only if predicate returns trueOr(Func<Option<T>>)- Provide alternative if NoneAnd<TOther>(Func<Option<TOther>>)- Combine two Options into tupleUnwrapOr(Func<T>)- Extract value or provide defaultUnwrapOrThrow<TException>(Func<TException>)- Extract value or throwTryUnwrap(out T?)- Try pattern for safe extraction
Map<TResult>(Func<TOk, TResult>)- Transform success valueMapError<TResult>(Func<TError, TResult>)- Transform error valueFlatMap<TResult>(Func<TOk, Result<TResult, TError>>)- Chain operationsRecover<TResult>(Func<TError, Result<TOk, TResult>>)- Attempt error recoveryValidate(Func<TOk, bool>, Func<TOk, TError>)- Add validationAnd<TOther>(Func<Result<TOther, TError>>)- Combine if both succeedOr<TOther>(Func<Result<TOk, TOther>>)- Try alternative on errorSwap()- Exchange success and error positionsTryUnwrap(out TOk?)/TryUnwrapError(out TError?)- Try patterns
Option.Some<T>(T)/Option.None<T>()- Create OptionsResult.Ok<TOk, TError>(TOk)/Result.Error<TOk, TError>(TError)- Create ResultsOption.All(IEnumerable<Option<T>>)- Combine Options (all must be Some)Option.Any(IEnumerable<Option<T>>)- Find first SomeResult.All(IEnumerable<Result<TOk, TError>>)- Combine Results (all must succeed)Result.Any(IEnumerable<Result<TOk, TError>>)- Find first successTry.Execute<TOk>(Func<TOk>)- Convert exceptions to ResultsTry<TException>.Execute<TOk>(Func<TOk>)- Catch specific exception types
Enumerate<T>()- Pair elements with indicesScan<TSource, TResult>()- Running accumulation with intermediatesSelectWhere<TSource, TResult>()- Combined Select+Where using OptionTakeWhileInclusive<T>()- Take while true, including first false
Configure discriminated union generation:
[DiscriminatedUnion(
MatchOn = MatchUnionOn.Deconstruct, // or Type, None
GeneratePolymorphicSerialization = true,
GeneratePrivateConstructor = true
)]
public partial record Command { /* ... */ }