Table of Contents

How to use Maybe<T>

Before deciding to use Maybe<T>, have a think about whether it's for you while reading Should I use Maybe<T>?

Once you've decided, here's how to get started:


Creating a Maybe<T>

So you've decided to use Maybe<T>!

Use the following method to create a Maybe<T>:

Alternatively you can use an extension method:

The underlying type T of the Maybe can be inferred, so it need only be explicitly declared if you need to ensure that a base class or interface is used. The creation method will also convert any Nullable to a Maybe.

public void Method1(DerivedType inCouldBeNull)
{
    // Declare a new Maybe: 
    var theMaybe1 = Maybe.WithValue(inCouldBeNull);
    var theMaybe2 = inCouldBeNull.ToMaybe();

    // When you need to specify the underlying type of the Maybe explicitly
    // (for example if you want to use a base class or interface).
    var theMaybe2 = Maybe.WithValue<BaseType>(inCouldBeNull);
}

The value, whether it's null or not, will be wrapped in a Maybe of the same underlying type. Of course, Maybe<T> cannot be used for passthrough null-checking in the same way as the Validate methods, because it changes the type of the parameter; but using Maybe<T> more widely through a codebase can have its benefits too, contributing to the overall robustness of the code.

There's a further creation method that can be used in circumstances where there's certainty that the value is not null:

The value of using this method is semantic - it allows that certainty to be expressed in the code, while maintaining compatibility with other uses of Maybe<T>. However, if for some reason the value is actually null against expectations, then an exception will be thrown.

public void Method2(DerivedType inCouldBeNull)
{
    // Throws exception if value is null; a guarantee but at a cost.
    // Can only be used when the type of the parameter is unambiguous.
    var theMaybe3 = Maybe.WithKnownValue(inCouldBeNull);

    // Throws exception if value is null; a guarantee but at a cost.
    var theMaybe4 = Maybe<BaseType>.WithKnownValue(inCouldBeNull);
}

Converting between nullable types and Maybe<T>

Converting back and forth between .NET nullable types and Maybe<T> is straightfoward; any type T, including nullable value types and nullable reference types, can be converted to a Maybe<T> using the extension method ToMaybe() ; and a Maybe<T> can be converted to a nullable type using the extension method ToNullable() [1], [2] .


Getting a value from Maybe<T>

There are a number of methods on Maybe<T> for retrieving a value from it safely:

  • GetValueOr(T) - you specify a default value to return if none exists.
  • GetValueOr(Func<T>) - you specify a factory method that will return a default value if none exists.
  • GetValueOrMaybe(Maybe<T>) - you specify an alternative Maybe<T> that will be returned if no value exists.
  • GetValueOrMaybe(Func<Maybe<T>>) - you specify a factory method that will return an alternative Maybe<T> if no value exists.
  • GetValueOrThrow(string) - will throw an InvalidOperationException with the specified message if no value exists.
  • GetValueOrEmpty() - available when the underlying type supports forms that can be considered "empty". There are overloads of this method for GUIDs, strings and collections.
  • ToString() - returns the result of calling ToString on the underlying value or the default value for the type (if either exists), or the empty string (if neither a value nor default exists).
  • TryGetValue(out T) - returns a Boolean indicating whether a value exists or not. If it does, the value of it will be assigned to the out parameter. If it doesn't, the out parameter will have the default value for T (which may be null).

Working with Maybes

Maybe<T> has a few more options for extracting values from it than Nullable<T> has, but so far we've seen nothing much to distinguish Maybe from Nullable. The next few methods are where that difference emerges. Some of these have supported aliases that may be more comfortable for developers familar with the theory behind Maybes, but in writing Existential.Net I've tried to emphasise usability over theory - so I'll mention the aliases here, then ignore them.

  • Apply(Func<T, Maybe<TResult>>) (alias: Bind) - You provide a function that converts a T to a Maybe<TResult>. A Maybe<TResult> will be returned.
  • Apply(Func<T, TResult>) (alias: Map/ Select) - You provide a function that converts a T to a TResult. A Maybe<TResult> will be returned.
  • DoEither(Func<T, TResult>, Func<TResult>) (alias: Match) - You provide two functions: one function that acts on a T, returns a TResult and will be used if a value exists; and another that takes no parameters, but still returns a TResult. It will be used if no value exists. A Maybe<TResult> will be returned.
  • GetValueOrMaybe(Maybe<T>) - You provide an alternative Maybe<T>. If the current Maybe instance has a value, it will return itself; if not it will return the provided alternative.
  • GetValueOrMaybe(Func<Maybe<T>>) - You provide a method that will return an alternative Maybe<T>. If the current Maybe instance has a value, it will return itself; otherwise it will return the alternative provided by the method.

Methods that act on a Maybe<T> and return a Maybe<TResult> can be chained together to apply a sequence of operations, perhaps with the underlying datatype changing, without giving up the "Maybeness" of the results - so it's not essential to know whether or not a value exists at any point when designing the sequence, reducing the amount of conditional code that has to be written. Of course, that conditionality exists, but it's hidden away and dealt with by the Maybe methods and doesn't intrude on the expression of more interesting business logic. (T and TResult needn't be different types, but the possibility that they can be is what gives Maybe<T> its power.)

There's an overload of DoEither that performs an Action without returning any values. It has its uses, but it can only be used to terminate a sequence. IfExists is another method that can only be used to end a sequence.


Using Linq syntax with Maybes

The value in a Maybe<T> can be accessed and transformed using Linq syntax. In the following example from is being used to refer to the value of theMaybe (which is a Maybe<string>) and the select clause transforms it into a Maybe<int>. If the Maybe<string> is empty, the result will be an empty Maybe<int>; so the "maybeness" is retained.

Maybe<int> theResult = from theText in theMaybe
                       select theText.Length;

Values from multiple Maybes can also be processed in a single Linq statement. In the following example the values of two Maybe<string> are being transformed into a Maybe containing a tuple of ints. If either of the string Maybes are empty, the tuple Maybe will also be empty - any empty value in a sequence of operations will produce an empty result.

Maybe<(int, int)> theResult = from theText1 in theMaybe1
                              from theText2 in theMaybe2
                              select (Length1: theText1.Length, Length2: theText2.Length);

The where clause can also be used. In the example below, if theMaybe contains a string value that contains the search string, the length of the string value will be retuned as the value of a Maybe<int>. If theMaybe is empty, or the where condition is not satisfied, the Maybe<int> will be empty.

Maybe<int> theResult = from theText in theMaybe
                       where theText.Contains("the search string")
                       select theText.Length;