EinsTools.Utilities.Functional 0.0.1-rc.3

This is a prerelease version of EinsTools.Utilities.Functional.
dotnet add package EinsTools.Utilities.Functional --version 0.0.1-rc.3
NuGet\Install-Package EinsTools.Utilities.Functional -Version 0.0.1-rc.3
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="EinsTools.Utilities.Functional" Version="0.0.1-rc.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EinsTools.Utilities.Functional --version 0.0.1-rc.3
#r "nuget: EinsTools.Utilities.Functional, 0.0.1-rc.3"
#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.
// 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 variable
  • let: Binds the result of a function to a variable
  • select: Maps the result of a function to a new option
  • where: 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 variable
  • let: Binds the result of a function to a variable
  • select: Maps the result of a function to a new result
  • where: 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 variable
  • let: Binds the result of a function to a variable
  • select: 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 variable
  • let: Binds the result of a (synchronous) function to a variable
  • select: 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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 61 10/11/2023

Version 0.0.1
- Initial version