Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

MayBe Monad: Usage Examples

Rate me:
Please Sign up or sign in to vote.
4.81/5 (30 votes)
30 Nov 2014MIT3 min read 24.6K   131   29   4
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 <a href="http://en.wikipedia.org/wiki/Monad_%28functional_programming%29">MayBe</a> 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:

C#
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:

C#
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:

C#
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.

C#
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.

C#
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:

C#
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

C#
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.

C#
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.

C#
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.

C#
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

C#
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

C#
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.

C#
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.

C#
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.

C#
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.

C#
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

C#
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:

C#
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
C#
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.

F#
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.

C#
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.

C#
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.

C#
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.

C#
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:

C#
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.

C#
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!

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United States United States
B.Sc. in Computer Science.

Comments and Discussions

 
QuestionNice Pin
Sacha Barber4-Dec-14 2:44
Sacha Barber4-Dec-14 2:44 
AnswerRe: Nice Pin
Sergey Morenko4-Dec-14 3:12
professionalSergey Morenko4-Dec-14 3:12 
QuestionInteresting idea Pin
Henrik Jonsson1-Dec-14 23:26
Henrik Jonsson1-Dec-14 23:26 
... and a well written article. You got my 5!

However, I wonder if we MayBe just can create generic extension methods for all nullable types avoiding to create Optional instances:

C#
public static class Monads
    {

        public static T Do<T>(this T value, Action<T> action) where T : class
        {
            if( value != null )
            {
                action(value);
            }
            return value;
        }

        public static T DoOnNull<T>(this T value, Action action) where T : class
        {
            if( value == null )
            {
                action();
            }
            return value;
        }

        public static U Map<T,U>(this T value, Func<T,U> func) where T : class
        {
            if( value != null )
            {
                return func(value);
            }
            else
            {
                return default(U);
            }
        }
     }

     ...


used like this:

C#
public void Test ( )
       {
           MonadsTest instance = null;
           var result =
               instance
                   .Do ( x => x.Process() )
                   .Map( x => x.GetResult());
       }


Is there something with monads that cannot be done with just extension methods?
AnswerRe: Interesting idea Pin
Sergey Morenko2-Dec-14 0:43
professionalSergey Morenko2-Dec-14 0:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.