A word of caution: Functional is currently a work in progress, and development is being done directly on the master branch, so the API can and will change at any time, without notice. Should you use this library at the present time, do so at your own risk!
Functional is my personal library of extensions, functions, objects and structures for writing functional code in C#. It's an amalgamation of the LaYumba.Functional,language-ext and CSharpFunctionalExtensions libraries copied, modified and extended to suit my own needs.
After reading the wonderful Functional Programming in C# by Enrico Buonanno (unaffiliated) I saw how functional programming concepts, techniques and structures could be extremely useful in my code bases. There are several libraries that exist currently that implement FP concepts in .NET for C#, however I found that not all of them quite had the structure or functionality that I was looking for, so instead I am creating, combining, modifying and extending these libraries, along with implementing my own structures and functions, to create a functional utility library that I can use in my own projects.
Functional is very "opinionated" and is used in several of my projects. It is not optimized for performance, but rather for readability and maintainability. It is not intended to be a "drop-in" replacement for the existing libraries, but rather a combination of them, with some of my own ideas and implementations.
Some of the features from Functional that I use most often are:
Result
andResult<T>
types for better handling of errors, expected, unexpected, exceptional and otherwise compared to throwing exceptionsOption
andOption<T>
types for better null and empty handlingValueObject<T>
for creating typed value objectsEnumeration<T>
andFlaggedEnumeration<T>
for creating strongly typed static enumeration value types, similar toValueObject<T>
but for enumerationsDynamicEnumeration<T>
for creating dynamic enumeration value types which can add new values at runtime
Functional.F
static class- Several useful structures and monads from functional programming,
like
Identity<T>
,Option<T>
,Either<TLeft, TRight>
- Extensions to
IEumerable<T>
andIEnumerable
- And more!
This library is not current available on NuGet, however once a stable version is released it will be.
At the moment, I choose to git submodule
this library into my projects, but you can also just copy the source files
into your project.
Reference Functional.Core
in your project, and you're good to go!
Should you want to use the static functions, add:
using static Functional.Core.F;
to your file.
If using C# 10, you can optionally add a global using static Functional.Core.F;
to your project file to make the
static available everywhere.
If using C# 10, you can optionally add a Usings.cs
file and add:
global using static Functional.Core.F;
to the file to make the static methods available everywhere within your project.
This is a quick overview of some of the features of Functional. For more information, please see the wiki.
Most of the structures of Functional have some sort of "base" class (Result
for Result<T>
, Option
for Option<T>
, etc) which define many implicit operations to make working with them easier. For example, Option<T>
as a conversion to and from T
. This allows for flexable and clear code, such as:
Option<int> option = Some(5);
int value = option;
public Option<int> GetSome() => 5; // 5 -> Some(5)
// or
public Option<int> GetNone() => Option.None; // None -> Option<int>
int value = GetSome().Value; // Some(5) -> 5
int value = GetNone(); // None -> default(int) -> 0
int value = GetNone().ValueOrThrow(); // None -> throws InvalidOperationException
In traditional C# programming, we have a few options for handling errors:
- Return
null
ordefault(T)
and hope that the caller checks for it - Return a
bool
orenum
to indicate success or failure, and return the result via anout
orref
parameter ( Try...(.., ref T result) pattern) - Throw an exception
All of these options have their own problems:
- Returning
null
ordefault(T)
can lead to ambiguity as to the actual result of the operation (isnull
a valid result, or an error?) and can lead toNullReferenceException
s if the caller doesn't check for it. - Returning a
bool
orenum
is not descriptive can throw away useful information about the error - Throwing an exception is costly, and exceptions should (ideally) only be used for actual exceptional circumstances, not for expected errors.
- None of these options allow for the composition of errors.
The Result
and various Error
fixes many of these problems:
- A
Result
is either aSuccess
or anFailure
, and is composed of a list ofError
s- Any given Result is a
Success
if and only if it has noError
s which are not expected - Functions which define a sequence of operations no longer need to check for errors after each operation, but can
instead compose the results of each operation into a single
Result
. - A
Result
is a monad, which allows for functional composition of operations usingBind
,Map
,Apply
, etc.
- Any given Result is a
// Create a Result
var result = Result.Success;
// Create a Result<T>
var result = Result.Success(42);
// Create a Result with an Error
var result = Result.Failure("Something went wrong"); // <- implicit conversion from string -> Error
// Create a Result<T> with an Error
var result = Result.Failure<int>("Something went wrong"); // <- implicit conversion from string -> Error
// Create a Result with multiple Errors
var result = Result.Failure("Something went wrong", "Something else went wrong"); // <- implicit conversion from string -> Error
// Combine Results
var result = Result.Combine(Result.Success(), Result.Success()); // <- Success
var result = Result.Combine(Result.Success(), "Something went wrong"); // <- Failure
// Add a Result to another Result
var result = Result.Success; // <- Success
result += Result.Success(43); // <- Success
result += Result.Failure("Something went wrong"); // <- Failure
// Add an Error to a Result
var result = Result.Success; // <- Success
result += "Something went wrong"; // <- Failure
result.WithError("Something else went wrong"); // <- Failure
// Implicit conversion within method to Result<T>
public Result<int> GetNumber() => 42; // <- Success(42)
public Result<int> GreaterThanZero(int number)
{
// Implicit conversion from int to Result<T>
if (number > 0) return number; // <- Success(number)
else return "Number must be greater than zero"; // <- Implicit conversion from string -> Result<int> (Failure)
}