EinsTools.Utilities.Functional
0.0.1-rc.3
dotnet add package EinsTools.Utilities.Functional --version 0.0.1-rc.3
NuGet\Install-Package EinsTools.Utilities.Functional -Version 0.0.1-rc.3
<PackageReference Include="EinsTools.Utilities.Functional" Version="0.0.1-rc.3" />
paket add EinsTools.Utilities.Functional --version 0.0.1-rc.3
#r "nuget: EinsTools.Utilities.Functional, 0.0.1-rc.3"
// Install EinsTools.Utilities.Functional as a Cake Addin #addin nuget:?package=EinsTools.Utilities.Functional&version=0.0.1-rc.3&prerelease // Install EinsTools.Utilities.Functional as a Cake Tool #tool nuget:?package=EinsTools.Utilities.Functional&version=0.0.1-rc.3&prerelease
EinsTools.Utilities.Functional
This library provides a set of functional programming tools for C#.
If offers
- option type, that handles optional values
- result type, that handles errors
- choice type, that handles multiple values
- several extension methods for functions
- and LINQ syntax for tasks
Option
The option type is a wrapper for optional values. It is a generic type, so it can wrap any type.
The purpose of the option type is to avoid null values and to handle optional values in a safe and convenient way.
Using options
You can create an option by using the static methods Some
and None
:
var some = Option.Some(42);
var none = Option.None<int>();
You can also use the implicit conversion from T
to Option<T>
:
Option<int> some = 42;
Matching options
You can use the Match
method to match an option:
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Match(
some: value => $"Some: {value}",
none: () => "None"); // Will return "Some: 42"
var noneResult = none.Match(
some: value => $"Some: {value}",
none: () => "None"); // Will return "None"
Binding options
You can use the Bind
method to bind an option to a function. If the option is None
, the function will
not be called. If the option is Some
, the function will be called with the value of the option.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Bind(value => $"{value}"); // Will return Some("42")
var noneResult = none.Bind(value => $"{value}"); // Will return None<string>()
You can use the Bind
method to chain multiple options:
var some = Option.Some(42);
var someResult = some
.Bind(value => Option.Some(value + 1))
.Bind(value => Option.Some(value + 1)); // Will return Some(44)
It is possible to use bind with async functions:
var some = Option.Some(42);
var someResult = await some
.BindAsync(async value => await Task.FromResult(Option.Some(value + 1)))
.BindAsync(async value => await Task.FromResult(Option.Some(value + 1))); // Will return Some(44)
Mapping options
Mapping is similar to binding, but it does not wrap the result in an option.
If the option is None
, the function will not be called. If the option is Some
, the function
will be called with the value of the option.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Map(value => $"{value}"); // Will return Some("42")
var noneResult = none.Map(value => $"{value}"); // Will return None<string>()
This is useful if you have a function that will always succeed.
Like with bind, you can also use async functions:
var some = Option.Some(42);
var someResult = await some
.MapAsync(async value => await Task.FromResult($"{value}")); // Will return Some("42")
LINQ Syntax
You can use LINQ syntax to work with options:
Option<int> CreateOption() => Option.Some(42);
Option<string> MyFunction(int value) => Option.Some($"some: {value}");
string ToUpper(string value) => value.ToUpper();
from value in CreateOption()
from value2 in MyFunction(value)
let value3 = ToUpper(value2)
select value3 // Will return Some("SOME: 42")
We support the following LINQ operators:
from
: Binds the value of the option to a variablelet
: Binds the result of a function to a variableselect
: Maps the result of a function to a new optionwhere
: Filters the option
Filtering with where works like this:
Option<int> CreateOption() => Option.Some(42);
bool IsEven(int value) => value % 2 == 0;
from value in CreateOption()
where IsEven(value) // Will return Some(42)
from value in CreateOption()
where !IsEven(value) // Will return None<int>()
Reduce
You can use the Reduce
method to reduce an option to a value. If the option is None
,
the default value will be returned.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Reduce(0); // Will return 42
var noneResult = none.Reduce(0); // Will return 0
Or
You can use the Or
method to combine two options. If the first option is Some
, it will be returned.
If the first option is None
, the second option will be returned.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Or(Option.Some(0)); // Will return Some(42)
var noneResult = none.Or(Option.Some(0)); // Will return Some(0)
And
You can use the And
method to combine two options. If the first option is Some
, the second option will be returned.
If the first option is None
, it will be returned.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.And(Option.Some(0)); // Will return Some(0)
var noneResult = none.And(Option.Some(0)); // Will return None<int>()
Do
Do can be used to create side effects. It will call a function with the value of the option,
if the option is Some
.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Do(value => Console.WriteLine(value)); // Will print "42"
var noneResult = none.Do(value => Console.WriteLine(value)); // Will do nothing
You can also use async functions:
var some = Option.Some(42);
var someResult = await some.DoAsync(async value => await Task.Run(() => Console.WriteLine(value))); // Will print "42"
Try
The try method works like the TryGet
function of the Dictionary
class.
If the option is Some
, it will return true
and the value of the option as an out parameter.
Otherwise it will return false
and the default value of the type as an out parameter.
var some = Option.Some(42);
var none = Option.None<int>();
var someResult = some.Try(out var value); // Will return true and set value to 42
var noneResult = none.Try(out var value); // Will return false and set value to 0
Result
The result type is a wrapper for values that can fail. It is a generic type, so it can wrap any type.
The purpose of the result type is to avoid exceptions and to handle errors in a safe and convenient way.
Using results
You can create a result by using the static methods Ok
and Failure
:
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
Matching results
You can use the Match
method to match a result:
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Match(
ok: value => $"Ok: {value}",
failure: error => $"Failure: {error.Message}"); // Will return "Ok: 42"
var failureResult = failure.Match(
ok: value => $"Ok: {value}",
failure: error => $"Failure: {error.Message}"); // Will return "Failure: error"
Binding results
You can use the Bind
method to bind a result to a function. If the result is Failure
, the function will
not be called. If the result is Ok
, the function will be called with the value of the result.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Bind(value => $"{value}"); // Will return Ok("42")
var failureResult = failure.Bind(value => $"{value}"); // Will return Failure<string>(new Exception("error"))
You can use the Bind
method to chain multiple results:
var ok = Result.Ok(42);
var okResult = ok
.Bind(value => Result.Ok(value + 1))
.Bind(value => Result.Ok(value + 1)); // Will return Ok(44)
It is possible to use bind with async functions:
var ok = Result.Ok(42);
var okResult = await ok
.BindAsync(async value => await Task.FromResult(Result.Ok(value + 1)))
.BindAsync(async value => await Task.FromResult(Result.Ok(value + 1))); // Will return Ok(44)
Mapping results
Mapping is similar to binding, but it does not wrap the result in a result.
If the result is Failure
, the function will not be called. If the result is Ok
, the function
will be called with the value of the result.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Map(value => $"{value}"); // Will return Ok("42")
var failureResult = failure.Map(value => $"{value}"); // Will return Failure<string>(new Exception("error"))
This is useful if you have a function that will always succeed.
Like with bind, you can also use async functions:
var ok = Result.Ok(42);
var okResult = await ok
.MapAsync(async value => await Task.FromResult($"{value}")); // Will return Ok("42")
The map function will capture exceptions and return a failure result:
var ok = Result.Ok(42);
int MyFunction(int value) => throw new Exception("error");
var okResult = ok.Map(value => MyFunction(value)); // Will return Failure<int>(new Exception("error"))
It can thus also be used to convert standard library functions that throw exceptions into result functions:
var ok = Result.Ok("NonExistingFile.txt");
var result = ok.Map(File.ReadAllText); // Will return Failure<string>(new FileNotFoundException("NonExistingFile.txt"))
LINQ Syntax
You can use LINQ syntax to work with results:
var ok = Result.Ok(42);
Result<string> MyFunction(int value) => Result.Ok($"some: {value}");
from value in ok
from value2 in MyFunction(value)
select value2 // Will return Ok("some: 42")
We support the following LINQ operators:
from
: Binds the value of the result to a variablelet
: Binds the result of a function to a variableselect
: Maps the result of a function to a new resultwhere
: Filters the result
Filtering with where works like this:
Result<int> CreateResult() => Result.Ok(42);
bool IsEven(int value) => value % 2 == 0;
from value in CreateResult()
where IsEven(value) // Will return Ok(42)
from value in CreateResult()
where !IsEven(value) // Will return Failure<int>(new Exception(...))
The let command can be used to convert standard library functions that throw exceptions into result functions:
var ok = Result.Ok("NonExistingFile.txt");
from value in ok
let value2 = File.ReadAllText(value)
select value2 // Will return Failure<string>(new FileNotFoundException("NonExistingFile.txt"))
Compose
You can use the Compose
method to compose two functions that return results:
Result<int> MyFunction(int value) => Result.Ok(value + 1);
Result<string> MyFunction2(int value) => Result.Ok($"some: {value}");
var composed = MyFunction.Compose(MyFunction2);
var result = composed(42); // Will return Ok("some: 43")
ComposeAsync
You can use the ComposeAsync
method to compose two async functions that return results:
Task<Result<int>> MyFunction(int value) => Task.FromResult(Result.Ok(value + 1));
Task<Result<string>> MyFunction2(int value) => Task.FromResult(Result.Ok($"some: {value}"));
var composed = MyFunction.ComposeAsync(MyFunction2);
var result = await composed(42); // Will return Ok("some: 43")
ToResultFunc
You can use the ToResultFunc
method to convert a function that returns a value into a function
that returns a result. If the function throws an exception, the exception will be captured and
returned as a failure result.
var fn = (int n) => n switch
{
>= 0 => n,
_ => throw new Exception("error")
};
var result = fn.ToResultFunc();
var okResult = result(42); // Will return Ok(42)
var failureResult = result(-1); // Will return Failure<int>(new Exception("error"))
Use ToResultFuncAsync
for async functions:
var fn = (int n) => Task.FromResult(n switch
{
>= 0 => n,
_ => throw new Exception("error")
});
var result = fn.ToResultFuncAsync();
var okResult = await result(42); // Will return Ok(42)
var failureResult = await result(-1); // Will return Failure<int>(new Exception("error"))
Do
Do can be used to create side effects. It will call a function with the value of the result,
if the result is Ok
.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Do(value => Console.WriteLine(value)); // Will print "42"
var failureResult = failure.Do(value => Console.WriteLine(value)); // Will do nothing
You can also use async functions:
var ok = Result.Ok(42);
var okResult = await ok.DoAsync(async value => await Task.Run(() => Console.WriteLine(value))); // Will print "42"
Or
You can use the Or
method to combine two results. If the first result is Ok
, it will be returned.
If the first result is Failure
, the second result will be returned.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Or(Result.Ok(0)); // Will return Ok(42)
var failureResult = failure.Or(Result.Ok(0)); // Will return Ok(0)
You can also use the async version.
Count
Count will return 1 if the result is Ok
and 0 if the result is Failure
.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.Count(); // Will return 1
var failureResult = failure.Count(); // Will return 0
Fold
Tbd.
ToArray/ToList/ToEnumerable/ToImmutableList/ToImmutableArray
These functions will return an enumerable with one element if the result is Ok
and an empty enumerable if the result is Failure
.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.ToArray(); // Will return new[] {42}
var failureResult = failure.ToArray(); // Will return new int[0]
ToOption
This function will return Some
if the result is Ok
and None
if the result is Failure
.
var ok = Result.Ok(42);
var failure = Result.Failure<int>(new Exception("error"));
var okResult = ok.ToOption(); // Will return Some(42)
var failureResult = failure.ToOption(); // Will return None<int>()
Choice
The choice type is a wrapper for multiple values. It is a generic type, so it can wrap any type.
The purpose of the choice type is to handle multiple values in a safe and convenient way.
Using choices
You can create a choice by using the static methods Left
and Right
:
var left = Choice.Left<int, string>(42);
var right = Choice.Right<int, string>("error");
Matching choices
You can use the Match
method to match a choice:
var left = Choice.Left<int, string>(42);
var right = Choice.Right<int, string>("error");
var leftResult = left.Match(
left: value => $"Left: {value}",
right: error => $"Right: {error}"); // Will return "Left: 42"
var rightResult = right.Match(
left: value => $"Left: {value}",
right: error => $"Right: {error}"); // Will return "Right: error"
Binding choices
You can use the Bind
method to bind a choice to a function. If the choice is Left
, the function will
not be called. If the choice is Right
, the function will be called with the value of the choice.
var left = Choice.Left<string, int>("error");
var right = Choice.Right<int, string>(42);
var leftResult = left.Bind(value => $"{value}"); // Will return Left<string, string>("error")
var rightResult = right.Bind(value => $"{value}"); // Will return Right<string, string>("42")
You can use the Bind
method to chain multiple choices:
var right = Choice.Right<int, string>(42);
var rightResult = right
.Bind(value => Choice.Right<int, string>(value + 1))
.Bind(value => Choice.Right<int, string>(value + 1)); // Will return Right<int, string>(44)
It is possible to use bind with async functions:
var right = Choice.Right<int, string>(42);
var rightResult = await right
.BindAsync(async value => await Task.FromResult(Choice.Right<int, string>(value + 1)))
.BindAsync(async value => await Task.FromResult(Choice.Right<int, string>(value + 1))); // Will return Right<int, string>(44)
Mapping choices
Mapping is similar to binding, but it does not wrap the result in a choice.
If the choice is Left
, the function will not be called. If the choice is Right
, the function
will be called with the value of the choice.
var left = Choice.Left<string, int>("error");
var right = Choice.Right<int, string>(42);
var leftResult = left.Map(value => $"{value}"); // Will return Left<string, string>("error")
var rightResult = right.Map(value => $"{value}"); // Will return Right<int, string>("42")
This is useful if you have a function that will always succeed.
Like with bind, you can also use async functions:
var right = Choice.Right<int, string>(42);
var rightResult = await right
.MapAsync(async value => await Task.FromResult($"{value}")); // Will return Right<int, string>("42")
LINQ Syntax
You can use LINQ syntax to work with choices:
var left = Choice.Left<int, string>(42);
Choice<string, string> MyFunction(int value) => Choice.Right<int, string>($"some: {value}");
from value in left
from value2 in MyFunction(value)
select value2 // Will return Left<int, string>(42)
We support the following LINQ operators:
from
: Binds the value of the choice to a variablelet
: Binds the result of a function to a variableselect
: Maps the result of a function to a new choice
Do
Do can be used to create side effects. It will call a function with the value of the choice,
if the choice is Right
.
var left = Choice.Left<string, int>("error);
var right = Choice.Right<string, int>(42);
left.Do(value => Console.WriteLine(value)); // Will do nothing
right.Do(value => Console.WriteLine(value)); // Will print "42"
You can also use async functions:
var right = Choice.Right<string, int>(42);
await right.DoAsync(async value => await Task.Run(() => Console.WriteLine(value))); // Will print "42"
Function Extensions
Apply
The Apply
method can be used to apply a function to a value. It returns a function where
the first argument is already applied.
Func<int, int, int> fn = (a, b) => a + b;
var fn2 = fn.Apply(1); // fn2 is now a function that takes one argument and adds 1 to it
var result = fn2(2); // Will return 3
We support up to 8 arguments.
WithDisposable
The WithDisposable
method can be used to create a function that will dispose a disposable object
after the function is called.
Func<Stream, byte> fn = stream => stream.ReadByte();
Func<Stream> createStream = () => new MemoryStream(new byte[] {1, 2, 3});
var result = fn.WithDisposable(createStream);
result(); // Will return 1
We support up to 8 arguments.
ToAsync
The ToAsync
method can be used to convert a function to an async function.
Func<int, int> fn = n => n + 1;
var asyncFn = fn.ToAsync();
var result = await asyncFn(42); // Will return 43
The function will be wrapped with Task.FromResult. So it will not be executed asynchronously, but it can be used in async contexts.
We support up to 8 arguments.
RunAsync
The RunAsync
method can be used to convert a function to an async function. The difference to
ToAsync
is that the body of the function will be executed asynchronously.
Func<int, int> fn = n => n + 1;
var asyncFn = fn.RunAsync();
var result = await asyncFn(42); // Will return 43
We support up to 8 arguments.
Do
The Do
method can be used to create side effects. It will call an action with the result of the function.
Func<int, int> fn = n => n + 1;
fn.Do(result => Console.WriteLine(result)); // Will print "43" and return 43
We support up to 8 arguments.
You can also use the DoAsync method to create side effects with async functions:
Func<int, Task<int>> fn = async n => await Task.FromResult(n + 1);
await fn.DoAsync(result => Console.WriteLine(result)); // Will print "43" and return 43
Curry
The Curry
method can be used to curry a function. It will return a function that takes one argument
and returns a function that takes the next argument and so on.
Func<int, int, int> fn = (a, b) => a + b;
var curried = fn.Curry();
var result = curried(1)(2); // Will return 3
We support up to 8 arguments.
Compose
The Compose
method can be used to compose two functions. fn.Compose(fn2)
means apply
fn2
to the result of fn
.
Func<int, int> fn = n => n + 1;
Func<int, int> fn2 = n => n * 2;
var composed = fn.Compose(fn2);
var result = composed(42); // Will return 86
If one of the functions is async, you can use the ComposeAsync
method:
Func<int, Task<int>> fn = async n => await Task.FromResult(n + 1);
Func<int, int> fn2 = n => n * 2;
var composed = fn.ComposeAsync(fn2);
var result = await composed(42); // Will return 86
Return
The Return
method can be used to create a function that always returns the same value.
Func<int, string, int> fn = Return(42);
var result = fn(42, "Hello"); // Will return 42
Id
The Id
method can be used to create a function that always returns the input value.
Func<int, int> fn = Id<int>();
var result = fn(42); // Will return 42
Task Extensions
We support the LINQ syntax for tasks.
var result =
from x in Task.FromResult(1)
from y in Task.FromResult(2)
select x + y;
We support the following LINQ operators:
from
: Binds the value of the task to a variablelet
: Binds the result of a (synchronous) function to a variableselect
: Maps the result of a function to a new task
Dictionary Extensions
GetOptionalValue
The GetOptionalValue
method can be used to get an optional value from a dictionary.
If the key exists, it will return Some
with the value. Otherwise it will return None
.
var dictionary = new Dictionary<int, string>
{
{1, "1"},
{2, "2"},
{3, "3"}
};
var result = dictionary.GetOptionalValue(4); // Will return None<string>()
var result2 = dictionary.GetOptionalValue(2); // Will return Some("2")
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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 was computed. 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. |
-
net7.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.0.1-rc.3 | 75 | 10/11/2023 |
Version 0.0.1
- Initial version