Optuple is a .NET Standard library that enables a tuple of Boolean and some
type (T
), i.e. (bool, T)
, to have the same semantics as an option type
found in most functional languages.
An option type is a discriminated union that either represents the absence of a value (none) or the value that's present (some). For example, F# has such a type that is defined as:
type Option<T> = | None | Some of T
Optuple, however, does not define any such type. Instead it primarily supplies
extension methods for any (bool, T)
(like Match
, Map
and more) to be
treated like an option type. Suppose a value x
of type T
, then
(true, x)
will be treated like Some x
and (false, _)
will
be treated like None
. Note that in the none case, when the first
element is false
, Optuple completely ignores and discards the second
element.
A library that wants to expose optional values needs no dependency on Optuple.
It can just expose (bool, T)
for some type T
. The client of the library
can use Optuple independently to get “optional semantics”.
To use Optuple simply import the following namespace:
using Optuple;
An auxiliary namespace is also provided:
using Optuple.Linq; // LINQ query syntax support
The most basic way to create optional values is to use the static Option
class:
var none = Option.None<int>();
var some = Option.Some(42);
Similarly, a more general extension method is provided, allowing a specified predicate:
string str = "foo";
var none = Option.SomeWhen(str, s => s == "bar"); // Return None if predicate is violated
var none = Option.NoneWhen(str, s => s == "foo"); // Return None if predicate is satisfied
Clearly, optional values are conceptually quite similar to nullables. Hence, a method is provided to convert a nullable into an optional value:
int? nullableWithoutValue = null;
int? nullableWithValue = 2;
var none = nullableWithoutValue.ToOption();
var some = nullableWithValue.ToOption();
When retrieving values, Optuple forces you to consider both cases (that is if a value is present or not).
Firstly, it is possible to check if a value is actually present:
var isSome = option.IsSome(); //returns true if a value is present
var isNone = option.IsNone(); //returns true if a value is not present
If you want to check if an option option
satisfies some predicate, you can
use theExists
method.
var isGreaterThanHundred = option.Exists(val => val > 100);
The most basic way to retrieve a value from an Option<T>
is the following:
// Returns the value if present, or otherwise an alternative value (42)
var value = option.Or(42);
Similarly, the OrDefault
function simply retrieves the default value for
a given type:
var none = Option.None<int>();
var value = none.OrDefault(); // Value 0
var none = Option.None<string>();
var value = none.OrDefault(); // null
In more elaborate scenarios, the Match
method evaluates a specified
function:
// Evaluates one of the provided functions and returns the result
var value = option.Match(x => x + 1, () => 42);
// Or written in a more functional style (pattern matching)
var value = option.Match(
some: x => x + 1,
none: () => 42
);
There is a similar Match
function to simply induce side-effects:
// Evaluates one of the provided actions
option.Match(x => Console.WriteLine(x), () => Console.WriteLine(42));
// Or pattern matching style as before
option.Match(
some: x => Console.WriteLine(x),
none: () => Console.WriteLine(42)
);
A few extension methods are provided to safely manipulate optional values.
The Map
function transforms the inner value of an option. If no value is
present none is simply propagated:
var none = Option.None<int>();
var stillNone = none.Map(x => x + 10);
var some = Option.Some(42);
var somePlus10 = some.Map(x => x + 10);
Finally, it is possible to perform filtering. The Filter
function returns
none, if the specified predicate is not satisfied. If the option is already
none, it is simply returned as is:
var none = Option.None<int>();
var stillNone = none.Filter(x => x > 10);
var some = Option.Some(10);
var stillSome = some.Filter(x => x == 10);
var none = some.Filter(x => x != 10);
If you statically import OptionModule
:
using static Optuple.OptionModule;
it will make the following common methods available for use without type qualification:
Some
None<>
SomeWhen
NoneWhen
This permits you to, for example, simply write Some(42)
and None<int>()
instead of Option.Some(42)
and None<int>()
, respectively.
Although options deliberately don't act as enumerables, you can easily convert
an option to an enumerable by calling the ToEnumerable()
method:
var enumerable = option.ToEnumerable();
By importing the Optuple.Collections
namespace, you also get the following
extension methods for sequences (IEnumerable<>
) that are like their LINQ
counterparts but return an option:
FirstOrNone
: likeFirstOrDefault
but returns an optionLastOrNone
: likeLastOrDefault
but returns an optionSingleOrNone
: likeSingleOrDefault
but returns an option
Then there is:
Filter
, which given a sequence of options, will return a sequence of x values from those options in the original sequence that are some x.ListAll
, which given a sequence of options, will return some list of x if all options in the original sequence are some x; otherwise it returns none list.
Optuple supports LINQ query syntax, to make the above transformations somewhat cleaner.
To use LINQ query syntax you must import the following namespace:
using Optuple.Linq;
This allows you to do fancy stuff such as:
var personWithGreenHair =
from person in FindPersonById(10)
from hairstyle in GetHairstyle(person)
from color in ParseStringToColor("green")
where hairstyle.Color == color
select person;
In general, this closely resembles a sequence of calls to FlatMap
and
Filter
. However, using query syntax can be a lot easier to read in complex
cases.
Two optional values are equal if the following is satisfied:
- The two options have the same type
- Both are none, both contain null values, or the contained values are equal
Credit: A large portion of this documentation was dervied from that of the project Optional. Thank you, Nils Lück!