Skip to content
bfren edited this page Jan 31, 2023 · 7 revisions

Contents

Functional or fluent?

Once we have used Some<T>(T) : Maybe<T> to lift a value into the world of Maybe<T>, we need to do something with it by chaining our functions together. The Maybe<T> class comes with a load of methods which are actually wrappers for the functions that do the work.

So, although you can use the functions directly, you'll find it much more convenient to write chains using the common C# fluent syntax. These two are functionally identical:

var maybe = F.Some(42);

// functional syntax
var functional = F.Map(maybe, x => x.ToString(), F.DefaultHandler);

// fluent syntax
var fluent = maybe.Map(x => x.ToString(), F.DefaultHandler);

In that snippet, functional and fluent are both Some<string> with Value "42".

From now on, I will give function signatures as their equivalent method on Maybe<T> because that should help keep things a little clearer, and it's what you will actually use.

Map<T, TReturn>(Func<T, TReturn>, Handler) : Maybe<TReturn>

Map does a switch and behaves like this:

  • if the input Maybe<T> is None<T>, return None<TReturn> with the original reason message
  • if the input Maybe<T> is Some<T>...
    • get the Value T
    • use that as the input and execute Func<T, TReturn>, then wrap the result in Some<TReturn>
    • catch any exceptions using Handler

See it in action here:

var result =
    F.Some(3)
        .Map( // x is 3
            x => x * 4,
            DefaultHandler
        )
        .Map( // x is 12
            x => x / 2,
            e => new M.DivisionFailedMsg(e)
        )
        .Map( // x is 6
            x => x * 7,
            F.DefaultHandler
        )
        .Map(
            x => $"The answer is {x}.",
            F.DefaultHandler
        )
        .AuditSwitch(
            some: val => Console.Write(val),
            none: msg => Console.Write(msg)
        );

public static class M
{
    public sealed record class DivisionFailedMsg(Exception Value) : IExceptionMsg { }
}

If you are using LINQPad to to you change the second Map in that sample so you have x / 0 instead of x / 2 and re-run the snippet, you will see that you still end up with an Maybe<string>, only this time it's None<string> with a DivisionFailedMsg as the reason message.

There are two additional things to note here:

  1. DefaultHandler is available for you to use, but you should use it sparingly, and not rely on it, unless you really don't care about having helpful messages.
  2. AuditSwitch(Action<T> some, Action<IMsg> none) is useful for logging - the 'some' action receives the current Value (if the input is Some<T>) and the 'none' action receives the current reason message (if the input is None<T>).

Clone this wiki locally