Skip to content

atifaziz/Optuple

Repository files navigation

Optuple

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”.

Usage

Using the library

To use Optuple simply import the following namespace:

using Optuple;

An auxiliary namespace is also provided:

using Optuple.Linq; // LINQ query syntax support

Creating optional values

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();

Retrieving values

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)
);

Transforming and filtering values

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);

Helpers as global methods

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.

Enumerating options

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:

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.

Working with LINQ query syntax

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.

Equivalence and comparison

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!