65.9K
CodeProject is changing. Read more.
Home

MayBe Monad: Usage Examples

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (30 votes)

Nov 30, 2014

MIT

3 min read

viewsIcon

25075

downloadIcon

131

MayBe monad: usage example

Introduction

Everybody has heard about monads and functional languages (Scala, F#, Haskell, etc.), so I will not talk about monad. This article about usage MayBe monad, you'll find a lot of samples.

When to Use MayBe

MayBe has a value or has no value. One of the popular examples in imperative programming language is null equals no value and not null is a value. It's too limited sample, because null could be a value. So, if you want to emphasize that object can contain nothing use MayBe monad. C# has Nullable<T> type but only for value type.

Hello, My Name is Option<T>

Here's the implementation of MayBe monad:

public sealed class Option<T>
{
    private static readonly Option<T> _empty = new Option<T>(default(T), false);
    private readonly bool _hasValue;

    public Option(T value, bool hasValue = true)
    {
        _hasValue = hasValue;
        Value = value;
    }

    public static Option<T> Empty
    {
        get { return _empty; }
    }

    public bool HasNoValue
    {
        get { return !_hasValue; }
    }

    public bool HasValue
    {
        get { return _hasValue; }
    }

    public T Value { get; private set; }
} 

Option<T> has the following extensions/methods:

Option<T> ToOption<T>(this T value)
Option<T> Do<T>(this Option<T> value, Action<T> action) 
Option<T> Do<T>(this Option<T> value, Func<T, bool> predicate, Action<T> action) 
Option<T> DoOnEmpty<T>(this Option<T> value, Action action) 
Option<TResult> Map<TInput, TResult>
    (this Option<TInput> value, Func<TInput, TResult> func) 
Option<TResult> Map<TInput, TResult>
    (this Option<TInput> value, Func<TInput, Option<TResult>> func)
Option<TResult> MapOnEmpty<TInput, TResult>
    (this Option<TInput> value, Func<TResult> func)
Option<T> Where<T>(this Option<T> value, Func<T, bool> predicate)
Option<T> Match(Func<T, bool> predicate, Action<T> action)
Option<T> MatchType<TTarget>(Action<TTarget> action)
Option<T> ThrowOnEmpty<TException>()
Option<T> ThrowOnEmpty<TException>(Func<TException> func)   

ToOption - creates an Option<T> instance from any type:

public static Option<T> ToOption<T>(this T value)
{
    if (typeof(T).IsValueType == false && ReferenceEquals(value, null))
    {
        return Option<T>.Empty;
    }
    return new Option<T>(value);
} 

In all sample, we'll use the same test classes: EmailRequest, Email, HappyEmail and QuickEmail. All classes are really simple.

public sealed class EmailRequest
{
    public string Recipient { get; set; }

    public bool IsValid()
    {
        if (string.IsNullOrWhiteSpace(Recipient))
        {
            return false;
        }
        return true;
    }

    public override string ToString()
    {
        return string.Format("Type: {0}, Recipient: {1}", typeof(EmailRequest).Name, Recipient);
    }
} 

From EmailRequest, we can create HappyEmail or QuickEmail.

public abstract class Email
{
    protected Email(EmailRequest request)
    {
        Recipient = request.Recipient;
    }

    public string Recipient { get; private set; }

    public static Email From(EmailRequest request)
    {
        int randomValue = new Random().Next(1000);
        if (randomValue % 2 == 0)
        {
            return new HappyEmail(request);
        }
        return new QuickEmail(request);
    }

    public override string ToString()
    {
        return string.Format("Type: {0}, FirstName: {1}", typeof(Email).Name, Recipient);
    }
} 

HappyEmail and QuickEmail are required for MatchType sample:

public sealed class HappyEmail : Email
{
    public HappyEmail(EmailRequest request) : base(request)
    {
    }
}

public sealed class QuickEmail : Email
{
    public QuickEmail(EmailRequest request)
        : base(request)
    {
    }
} 

EmailRequest is created through GetEmailRequest

private static EmailRequest GetEmailRequest()
{
    return new EmailRequest { Recipient = "John Doe" };
} 

Now, we are ready to start.

Do Sample

This example shows how to execute an action if email request is not null.

private static void DoSample()
{
    GetEmailRequest()
        .ToOption()
        .Do(x => ExecuteAction(x));
} 

As you can see, the code is very readable.

Do - just executes an action if an Options<T> has a value.

public static Option<T> Do<T>(this Option<T> value, Action<T> action)
{
    if (value.HasValue)
    {
        action(value.Value);
    }
    return value;
} 

Do returns the same input value, so you can execute as many actions as you want.

private static void DoSample()
{
    GetEmailRequest()
        .ToOption()
        .Do(x => ExecuteAction(x))
        .Do(x => ExecuteOtherAction(x));
} 

Where Sample

This example shows how to filter data, for example we want execute an action only on valid EmailRequest

private static void DoWithWhereSample()
{
    GetEmailRequest()
        .ToOption()
        .Where(x => x.IsValid())
        .Do(x => ExecuteAction(x));
} 

Where returns the value if predicate returns true, otherwise Option<T>.Empty

public static Option<T> Where<T>(this Option<T> value, Func<T, bool> predicate)
{
    if (value.HasNoValue)
    {
        return Option<T>.Empty;
    }
    return predicate(value.Value) ? value : Option<T>.Empty;
} 

Map Sample

Another simple example. We want to check EmailRequest, if a request is valid create an Email and execute an action on the email instance.

private static void MapSample()
{
    GetEmailRequest()
        .ToOption()
        .Where(x => x.IsValid())
        .Map(x => Email.From(x))
        .Do(x => ExecuteAction(x));
} 

Here's only one new function, it's - Map. Map executes a Func on input value.

public static Option<TResult> Map<TInput, TResult>(this Option<TInput> value, Func<TInput, TResult> func)
{
    if (value.HasNoValue)
    {
        return Option<TResult>.Empty;
    }
    return func(value.Value).ToOption();
}  

MapOnEmpty Sample

The opposite for Map is MapOnEmpty function, i.e., the function executes only if the Option has no value.

private static void MapOnEmptySample()
{
    Option<EmailRequest>.Empty
                        .DoOnEmpty(() => Console.WriteLine("Empty"))
                        .MapOnEmpty(() => GetEmailRequest())
                        .Do(x => ExecuteAction(x));
} 

This sample shows how to create EmailRequest if the input value doesn't have one.

public static Option<T> MapOnEmpty<T>(this Option<T> value, Func<T> func)
{
    if (value.HasNoValue)
    {
        return func().ToOption();
    }
    return value;
} 

Almost the same code with if

private static void MapOnEmptySample(EmailRequest request)
{
    if (request == null)
    {
        Console.WriteLine("Empty");
        request = GetEmailRequest();
        ExecuteAction(request);
    }
} 

Match Sample

Match is similar to Map function, but it executes an Action<T> if input predicate is true:

public Option<T> Match(Func<T, bool> predicate, Action<T> action)
{
    if (HasNoValue)
    {
        return Empty;
    }
    if (predicate(Value))
    {
        action(Value);
    }
    return this;
} 

For instance, we have to:

  • validate an input email request
    • if it is valid, create an email
    • check recipient
      • if it is lucky, execute the action
    • execute the action
private static void MatchSample()
{
    GetEmailRequest()
        .ToOption()
        .Where(x => x.IsValid())
        .Map(x => Email.From(x))
        .Match(x => IsLuckyRecipient(x.Recipient), x => ExecuteLuckyAction(x))
        .Do(x => ExecuteAction(x));
} 

Here's more information about Match on F#, take a look, it's really useful. Example from the article.

let constructQuery personName = 
    match personName with
    | FirstOnly(firstName) -> printf "May I call you %s?" firstName
    | LastOnly(lastName) -> printf "Are you Mr. or Ms. %s?" lastName
    | FirstLast(firstName, lastName) -> printf "Are you %s %s?" firstName lastName 

MatchType Sample

MatchType is similar to Match function, but executes an Action<Target> only if type of input value is the same as target type.

public Option<T> MatchType<TTarget>(Action<TTarget> action)
    where TTarget : T
{
    if (HasNoValue)
    {
        return Empty;
    }
    if (Value.GetType() == typeof(TTarget))
    {
        action((TTarget)Value);
    }
    return this;
} 

This sample shows how to execute different action on different types.

private static void MatchTypeSample()
{
    GetEmailRequest()
        .ToOption()
        .Where(x => x.IsValid())
        .Map(x => Email.From(x))
        .MatchType<HappyEmail>(x => Console.WriteLine("Happy"))
        .MatchType<QuickEmail>(x => Console.WriteLine("Quick"))
        .Do(x => Console.WriteLine(x));
} 

The sample is more difficult than others, so here's a detailed description:

  • First of all, we validate EmailRequest
  • Create HappyEmail or QuickEmail from email request
  • Write "Happy" if HappyEmail was created in Email.From factory method
  • Write "Quick" if QuickEmail was created in Email.From method

SelectMany Sample

All previous samples are similar, it shows how to work with one value, but what if we have more than one Option and we want to execute an action only if all Options have value. Ok, let's do it. I believe all of you know about Duck typing and Enumerable. Here is another one duck typing sample.

Option<string> lucky = from x in CreateEmail()
                       from y in Option<Email>.Empty
                       select SelectLucky(x.Recipient, y.Recipient); 

We can write this kind of code only if it has the following extension method, i.e., use select keyword.

public static Option<V> SelectMany<T, U, V>(this Option<T> value, 
    Func<T, Option<U>> func, Func<T, U, V> selector)
{
    return value.Map(x => func(x).Map(y => selector(x, y).ToOption()));
}

Here's the full sample:

private static void SelectWithEmpySample()
{
    Option<string> lucky = from x in CreateEmail()
                           from y in Option<Email>.Empty
                           select SelectLucky(x.Recipient, y.Recipient);

    lucky.Do(x => Console.WriteLine("First"))
         .DoOnEmpty(() => Console.WriteLine("Nobody"));
}  

SelectLucky method is executed only if x and y have a value, in this case y has no value, so "Nobody" is printed.

If all options have a value, like in the below example. So, "First" is printed.

private static void SelectSample()
{
    Option<string> lucky = from x in CreateEmail()
                           from y in CreateEmail()
                           select SelectLucky(x.Recipient, y.Recipient);

    lucky.Do(x => Console.WriteLine("First"))
         .DoOnEmpty(() => Console.WriteLine("Nobody"));
} 

As you can see, MayBe monad can be very useful. That's all folks!