This project includes types and higher-order functions adopted from functional programming.
The Option<T>
type encapsulates an optional value. It is usefull when the actual value (of type T
) might not exist. Option
is defined as a union type with two cases: Some
and None
.
var someInt = Option.Some(42); // creates an instance of Some<int>
var noneInt = Option.None<int>(); // creates an instance of None<int>
var someInt = 5.ToOption(); // creates Some<int>
string s = null;
var noneString = s.ToOption(); // yields None<string>
Option<string> s = "hello"; // creates an instance of Some<string>
int? i = 42;
var o = i.ToOption(); // creates an instance of Some<int>
If null
is passed as an argument to Option.Some<T>(T value)
it will yield None
:
var none = Option.Some<string>(null); // the result is None<string>
The extraction of the value should be easy but safe. It is supposed to be made difficult to extract the value when it is null (in case of None
) to prevent any kind of NullReferenceExceptions. Also the consumer should be forced to handle both cases of an exisitng value (Some) and non-existing value (None). (This concept is close to pattern matching from FP, e.g. F#.)
The value can be retrieved by calling the Match
method. This method has the following signature:
TResult Match<TResult>(Func<T, TResult> onSome, Func<TResult> onNone)
or in F# notation: ((T -> TResult)*(unit -> TResult)) -> TResult
Option.Some(49.9m)
.Match(
x => String.Format("Result = {0} %", x.ToString("F")),
() => "An error occurred.");
The DefaultIfNone(T defaultValue)
extension method will return the inner value of an Option instance. If the Option is None
a default value that is specified as an argument will be returned.
var x = Option.None<int>().DefaultIfNone(-1); // x will be -1
var y = Option.Some(42).DefaultIfNone(-1); // y will be 42
Choice<T1, T2>
is a type which represents either a value of type T1
or a value of type T2
.
Create an instance of Choice
that represents Choice 1 of 2:
var c = Choice.NewChoice1Of2<decimal, string>(2.5m);
Create an instance of Choice
that represents Choice 2 of 2:
var c = Choice.NewChoice2Of2<decimal, string>("An error occurred.");
An instance of Choice
can be created from an instance of Option
. If the Option is Some then the Choice will represent the value of the Option instance.
var c = Option.Some(42).ToChoice("No value specified."); // c represents 42
If the Option is None it will represent an alternative value which was passed as an argument.
var c = Option.None<int>().ToChoice("No value specified."); // c represents "No value specified"
The extraction of the inner value of the choice type can be done with Match()
similar to the extraction of Option values.
var result = Choice.NewChoice1Of2<string, int>("world")
.Match(
onChoice1Of2: x => String.Format("Hello {0}", x),
onChoice2Of2: x => String.Format("The number is {0}", x));
Fun.Create()
helps with creating instances of Func
.
This
var f = new Func<decimal, decimal, decimal, Option<decimal>((x1, x2, x3) => (x1 + x2) / x3));
can be written as
var f = Fun.Create((decimal x1, decimal x2, decimal x3) => (x1 + x2) / x3));
The extension method ReturnOption()
takes the result from a fuction and returns it wrapped in the option type. If the result is null it returns None
.
var f = Fun.Create((int i) => i%2 == 0 ? "foo" : null)
.ReturnOption(); // returns Option.None<string> if an odd number is passed as an argument
The extension method OnException()
internally wraps a try catch
around the execution and returns None
if an exception is thrown. It only extends functions with return type Option
.
var result = Fun.Create((decimal x, decimal y) => x/y)
.ReturnOption()
.OnExceptionNone(); // if 0 is passed as the second argument result will be None<decimal>
There are no parsing function included. But it is very easy to create them if needed with any operation that might return null or throw an exception.
var parseInt = Fun.Create((string s) => Int32.Parse(s)).ReturnOption().OnExceptionNone();
var i = parseInt("sdfs"); // i will be Option.None<int>
Curry()
returns the curried version of a function. (This is i.e necessary for the option type to act like an applicative functor.)
var result = Fun.Create((decimal x, decimal y) => x/y).Curry(); // yields Func<decimal, Func<decimal, decimal>>
var result = Fun.Create((decimal x, decimal y) => x/y)
.ReturnOption()
.OnExceptionNone()
.Curry() // returns Func<decimal, Func<decimal, Option<decimal>>
.ToOption() // lifts the function into the option type
.Apply(ReadDecimal()) // applies the result from ReadDecimal() and returns Func<decimal, Option<decimal>>
.Apply(ReadDecimal()) // applies the result from ReadDecimal() and returns Option<decimal>
.Select(x => x * 100) // maps a function over the inner value if option is Some
.Match(
x => String.Format("Result = {0} %", x.ToString("F")),
() => "An error occurred.");
Console.WriteLine(result);
Also the Linq query syntax is supported.
var output =
(
from v1 in ReadDecimal()
from v2 in ReadDecimal()
from result in Divide(v1, v2)
select result * 100
)
.Match(
x => String.Format("Result = {0} %", x.ToString("F")),
() => "An error occurred.");
Console.WriteLine(output);
(
from v1 in ReadDecimal().ToChoice(Failure.Create(Error.CannotNotParse1StInput))
join v2 in ReadDecimal().ToChoice(Failure.Create(Error.CannotNotParse2NdInput)) on 1 equals 1
from result in Divide(v1, v2).ToChoice(Failure.Create(Error.CannotDivideByZero))
select result
)
.Match(
x => Console.WriteLine("Result = {0}", x),
errors => errors.ToList().ForEach(x => Console.WriteLine(x.GetDisplayName())));