MayBe Monad: Usage Examples






4.81/5 (30 votes)
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
orQuickEmail
from email request - Write "
Happy
" ifHappyEmail
was created inEmail.From
factory method - Write "
Quick
" ifQuickEmail
was created inEmail.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 Option
s 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!