A comprehensive C# library that brings practical functional programming tools to everyday .NET development. It provides monads, discriminated unions, memoisation helpers, currying and partial application utilities, and a small set of LINQ-style extensions to make common functional patterns concise and expressive.
If you find this library useful and would like to support its continued development, please consider one of the options below. Your support helps fund maintenance, documentation and new features.
- Join my Patreon
- Donate via PayPal
- Buy Me a Coffee
- Subscribe on Twitch
- Subscribe on YouTube
- Purchase from my Amazon Wishlist
- Visit my website
Install the package from NuGet:
dotnet add package ApacheTech.Common.FunctionalCSharpAlternatively, add the ApacheTech.Common.FunctionalCSharp project to your solution and reference it directly during development.
This section summarises the principal APIs and how they are intended to be used.
Monads in this library are pragmatic tools for modelling optional values and fallible computations.
-
Maybe<T>represents an optional value. UseActual<T>for present values andNothing<T>for absence. Companion extensions such asToMaybe()andBind()make chaining straightforward and safe. -
Identity<T>is a minimal wrapper that carries a value and supports implicit conversions. It is useful when a monadic interface is desired without special semantics for missing values. -
Attempt<T>models computations that may fail.Success<T>contains the result;Fail<T>contains an exception and short-circuits subsequent operations.
Usage guidance: prefer Maybe where absence is a valid state and Attempt where exceptions should be modelled explicitly in the computation chain.
OneOf<T1, T2> holds one of two possible types. Use Match or Invoke to handle each case explicitly. This pattern is useful when a function may return one of a small set of distinct types (for example, a result or an error value) and you want callers to handle both cases explicitly.
Memento<TInput, TOutput> decorates a pure function with a dictionary-backed cache. It offers implicit conversions to and from Func<TInput, TOutput> to ease substitution of decorated and plain functions.
Important caveats: use only for pure, deterministic functions, and ensure TInput is a suitable dictionary key (immutable or with stable equality semantics).
Apply(partial application) binds leading arguments of a function to produce a function of smaller arity, which can be useful for creating specialised functions.Curryconverts a multi-argument function into nested single-argument functions, facilitating composition and reuse in functional pipelines.
Both families include overloads for a broad range of arities.
Mapapplies a transformation to a value and returns the result.Altattempts a primary mapping and uses a fallback when the primary result is a default value.Forkexecutes multiple independent paths against the same input and combines the results using a provided join function.TryValidatereturns whether all predicates hold and provides the set of failing rules;Validatesimply returns a boolean.
These helpers aim to make common patterns succinct and readable.
A small set of helpers reduces boilerplate around common error handling patterns. TryCatch<TException> wraps an Action with a handler for the specified exception type; convenience variants include TryOrFailFast, TryOrThrow, TryOrLogToConsole and TryOrLogToDebug.
IsDefaultValue<T> tests whether a value equals the default for its type and is useful for concise default checks.
This section contains compact examples illustrating typical usage patterns.
Parse, transform and format only when each step succeeds. The chain short-circuits on failure.
using ApacheTech.Common.FunctionalCSharp.Monads;
using ApacheTech.Common.FunctionalCSharp.Monads.Extensions;
var formatted = "123"
.ToMaybe()
.Bind(i => (i * 2).ToMaybe())
.Map(i => i.ToString());Use OneOf to return one of two types and handle both cases explicitly.
var result = new OneOf<int, string>(404);
string message = result.Match(
code => $"Error code: {code}",
text => $"Message: {text}"
);Decorate a pure function with Memento to cache results for repeated inputs.
Func<string, string> computeKey = s => s.ToUpperInvariant(); // expensive
var memoKey = new Memento<string, string>(computeKey);
var k1 = memoKey.Invoke("abc"); // computed
var k2 = memoKey.Invoke("abc"); // cachedRun multiple predicates and inspect failures.
var user = new User { Name = "", Age = 12 };
var ok = user.TryValidate(out var failed,
u => !string.IsNullOrWhiteSpace(u.Name),
u => u.Age >= 13);
if (!ok)
{
foreach (var rule in failed) Console.WriteLine("Validation failed");
}Contributions, bug reports and improvements are welcome. Please open issues or pull requests against the GitHub repository. When contributing, please follow the existing code style and add unit tests for new behaviours.
- Run
dotnet testin the test project before submitting changes.
This project is released under the terms of the licence included in LICENCE.md.