A functional datatype library for C#
The following types are currently provided
This class represents a void return value without a functionality.
Usually this return type communicates a side effect.
// The Unit object can only be instantiated by its instance getter;
return Unit.Instance;
It can be used for example as a return type of the Result type (see below), if one is not interested in the result value of a function but rather in the fact, that a function is a success or failure.
function Result<Unit> DoSomethingWithSideEffect(string anInput)
{
Console.WriteLine(anInput);
return Result.Success(anInput);
}
This is an implementation of the Option monad.
It represents an optional value. It's internal value is never be null!
// Construction through factory methods
var someValue = Option.Some(53); // Option<int>
var noValue = Option.None<int>(); // Option<int>
// Explicit null content. Use this with care as this breaks the intent of an option type
var nullContent = Option.Some<string>(null);
// Construction through the simple extension method AsOption()
var someValue1 = "FOO".AsOption(); // Creates an Option.Some("FOO")
// Null values are considered as "No Value" on construction through "AsOption"
string nullString = null;
var noValue1 = nullString.AsOption(); // Creates an Option.None<string>)
// Construction through extension method with a predicate over the value
var someValue2 = "FOO".AsOption(s => s.StartsWith("F")); // Creates an Option.Some("FOO")
var noValue2 = "FOO".AsOption(s => s.StartsWith("K")); // Creates an Option.None<string>()
var some = Option.Some("FOO");
Assert.IsEqual(true, some.HasValue);
Assert.IsEqual(false, some.HasNoValue);
var none = Option.None<string>();
Assert.IsEqual(false, none.HasValue);
Assert.IsEqual(true, none.HasNoValue);
Assert.IsEqual("FOO", Option.Some("FOO").ResultValueOr("BAR"));
Assert.IsEqual("BAR", Option.None<string>().ResultValueOr("BAR"));
If an option has a value, then the given action is executed. The option is returned for chaining purposes.
var some = Option.Some("FOO");
some.Do(Console.WriteLine); // Prints "FOO" on the console
var none = Option.None<string>();
none.Do(Console.WriteLine); // Does nothing
If an option has NO value, then the given action is executed. The option is returned for chaining purposes.
var some = Option.Some("FOO");
some.OtherwiseDo(() => Console.WriteLine("I have no value")); // Does nothing
var none = Option.None<string>();
none.OtherwiseDo(() => Console.WriteLine("I have no value")); // Prints "I have no value" on the console
// Chaining:
ISomeService service = ....
var option = service.GetSomeValue(); // Returns an Option<string> for example
option
.Do(v => Console.WriteLine("Got a value with content: {0}", v)
.OtherwiseDo(() => Console.WriteLine("Got no value"));
This comes in three overloads and matches either a Some or a None or has function parameters for both variants.
These are essentially aliases for Do
, OtherwiseDo
and .Do(...).OtherwiseDo(...)
, so the above examples
could also be written like so:
var some = Option.Some("FOO");
some.Match(() => Console.WriteLine("I have no value")); // Does nothing
some.Match(v => Console.WriteLine("I have the value {0}", v)); // Writes "I have the value FOO"
var none = Option.None<string>();
none.Match(() => Console.WriteLine("I have no value")); // Writes "I have no value"
none.Match(v => Console.WriteLine("I have the value {0}", v)); // Does nothing
// Chaining:
ISomeService service = ....
var option = service.GetSomeValue(); // Returns an Option<string> for example
option.Match(
v => Console.WriteLine("Got a value with content: {0}", v),
() => Console.WriteLine("Got no value"));
Transforms the value of an option with the help of a transformation function.
Select
is an alias for Map
, for better compatibility with LINQ.
Map catches no Exceptions, so you should use TryMap
for unsafe transformation functions.
var some = Option.Some(42);
var none = Option.None<int>();
var someString = some.Map(v => v.ToString()); // returns an Option.Some("42");
var noString = none.Map(v => v.ToString()); // returns an Option.None<string>();
Same as Map
/Select
but the transformation function is wrapped in a try-catch-block.
If the transformation function throws an exception, then a None
is returned.
var some = Option.Some(42);
var none = Option.None<int>();
var someString = some.TryMap(v => v.ToString()); // returns an Option.Some("42");
var someString = some.TryMap((string v) => throw new Exception("BLA")); // returns an Option.None<string>();
var noString = none.TryMap(v => v.ToString()); // returns an Option.None<string>();
Similar to Map
/TryMap
Bind
and TryBind
transform the option into another type.
In this case the transformation function returns also an Option.
var some = Option.Some(42);
var someString = some.Bind(v => Option.Some("FOO" + v)); // return Option.Some("FOO42"));
var noneString = some.Bind(_ => Option.None<string>()); // return Option.None<string>();
Filters an option by its predicate, so for a Some
a Some
or a None
is returned, depending if the predicate
matches or not. If
is here an alias for Where
and Unless
is an alias for WhereNot
.
var some = Option.Some(42);
var none = some.Where(v => v < 40); // Option.None<int>()
var someOther = some.Where(v => v > 40); // Option.Some(42)
var stillSomeOther = some.WhereNot(v => v < 40); // Option.Some(42)
var otherNone = some.WhereNot(v => v > 40); // Option.None<int>()
Implementation of an Either Type / Tagged Union
var leftValue = Either.Left<int, string>(42);
var rightValue = Either.Right<int, string>("FOO");
Executes an action on the matching value
var leftValue = Either.Left<int, string>(42);
var rightValue = Either.Right<int, string>("FOO");
leftValue.MatchLeft(v => Console.WriteLine("Left value is {0}", v)); // Prints "Left value is 42"
leftValue.MatchRight(v => Console.WriteLine("Right value is {0}", v)); // Does nothing
rightValue.MatchLeft(v => Console.WriteLine("Left value is {0}", v)); // Does nothing
rightValue.MatchRight(v => Console.WriteLine("Right value is {0}", v)); // Prints "Right value is Foo"
leftValue.Match(
v => Console.WriteLine("Left value is {0}", v),
v => Console.WriteLine("Right value is {0}", v)); // Prints "Left value is 42"
rightValue.Match(
v => Console.WriteLine("Left value is {0}", v),
v => Console.WriteLine("Right value is {0}", v)); // Prints "Right value is FOO"
Returns a value depending on the value of the instance.
var leftValue = Either.Left<int, string>(42);
var rightValue = Either.Right<int, string>("FOO");
var value1 = leftValue.Case(_ => true, _ => false);
Assert.IsEqual(true, value1)
var value2 = rightValue.Case(_ => true, _ => false);
Assert.IsEqual(false, value2)
This is a special Either
type representing a Result, which can have a success value
and a failure of type ExceptionWithContext
It communicates that the outcome can be either a success (with a value) or a failure.
var success = Result.Success(42);
var exceptionContext = new Dictionary<string, object>
{
{ "Key1", "Value1" },
( "Key2", new MessageObject() )
};
var failure = Result.Failure<int>(
new ExceptionWithContext(
"My super awesome message",
exceptionContext));
var failure2 = Result.Failure<int>("My super awesome message");
var failure3 = Result.Failure<int>("My super awesome message", context);
var failure4 = Result.Failure<int>("My super awesome message", new Exception("Inner exception"), context);
As the Result
type is just a specialized Either
type, all methods on Either
work also on Result
.
For convenience there are two additional aliases DoOnSuccess
and DoOnFailure
which map directly
to MatchLeft
resp. MatchRight
on the Either
type.
This is the implementation of a standard exception with additional context. The context is stored in a dicitionary.
This Exception is used as right value for the Result
type.
There is a helper method on the Exception for getting context values out of the Exception:
public Option<TResult> GetContextValue<TResult>(string key)
{
if (!_context.ContainsKey(key))
return Option.None<TResult>();
try
{
return ((TResult)_context[key]).AsOption();
}
catch (InvalidCastException)
{
return Option.None<TResult>();
}
}
- Mac/Linux:
./build.sh
- Windows:
build.cmd
(on CMD) or.\build.ps1
(on PowerShell)
Currently all unit tests are written with xUnit.