Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Standard Operations on Option<T> monad beyond Bind and Map

I'm using a C# implementation of F# option<`a`> monad from Petricek book (Real World Functional Programming):

internal enum OptionType { Some, None }

internal abstract class Option<T>
{
    private readonly OptionType tag;

    protected Option(OptionType tag)
    {
        this.tag = tag;
    }

    public OptionType Tag
    {
        get { return this.tag; }
    }

    public bool MatchNone()
    {
        return this.Tag == OptionType.None;
    }

    public bool MatchSome(out T value)
    {
        if (this.Tag == OptionType.Some)
        {
            value = ((Some<T>)this).Value;
        }
        else
        {
            value = default(T);
        }
        return this.Tag == OptionType.Some;
    }
}

internal sealed class None<T> : Option<T>
{
    public None() : base(OptionType.None) { }
}

internal sealed class Some<T> : Option<T>
{
    private readonly T value;

    public Some(T value)
        : base(OptionType.Some)
    {
        this.value = value;
    }

    public T Value
    {
        get
        {
            return this.value;
        }
    }
}

internal static class Option
{
    public static Option<T> None<T>()
    {
        return new None<T>();
    }

    public static Some<T> Some<T>(T value)
    {
        return new Some<T>(value);
    }
}

internal static class OptionExtensions
{
    public static Option<T2> Bind<T1, T2>(this Option<T1> option, Func<T1, Option<T2>> func)
    {
        T1 value1;
        if (option.MatchSome(out value1))
        {
            return func(value1);
        }
        return Option.None<T2>();
    }

    public static Option<T2> Map<T1, T2>(this Option<T1> option, Func<T1, T2> func)
    {
        T1 value1;
        if (option.MatchSome(out value1))
        {
            return Option.Some(func(value1));
        }
        return Option.None<T2>();
    }
}

Now I need an operation to extract the value if it's not None or returns a default.

I was wondering if this is possible using a combination of Map and Bind, but I think is not.

So I'm back to F# documentation, it gave me hints on some other useful extension methods to add, but not exactly what I need.

I've designed this function to fulfill my need:

public static T2 Return<T1, T2>(this Option<T1> option, Func<T1, T2> func, T2 noneValue)
{
    T1 value1;
    if (option.MatchSome(out value1))
    {
        return func(value1);
    }
    return noneValue;
}

So to not reinvent the wheel, the question is: is there's a reference or common functional patterns to define operations on Option<T> monad? Is correct add new operations on need?

like image 866
jay Avatar asked Apr 11 '13 17:04

jay


People also ask

What is the purpose of a monad?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What are monads explain with example?

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it. For example, you can create a type to wrap another one, in Haskell: data Wrapped a = Wrap a. To wrap stuff we define return :: a -> Wrapped a return x = Wrap x.

Is map a monad?

Map is not one of the defining properties of monads, however, because it's technically just a special case of FlatMap. A lifting function like Unit will wrap its object in a container, even if that object is itself the same type of container.

What are monads in functional programming?

In functional programming, a monad is a software design pattern with a structure that combines program fragments (functions) and wraps their return values in a type with additional computation.


1 Answers

A monad where you can always extract a value is usually a comonad. You know that a monad M<T> has methods (in C# syntax)

static M<T> Unit<T>(T t) { ... }
static M<R> Bind<A, R>(M<A> ma, Func<A, M<R>> func) { ... }

Or, alternatively, you can make a monad out of

static M<T> Unit<T>(T t) { ... }
static M<R> FMap<A, R>(M<A> ma, Func<A, R> func) { ... }
static M<T> Join<T>(M<M<T>> mmt) { ... }

The two characterizations are equivalent; you can construct one given implementations of the other.

A comonad has operations

static T Extract<T>(M<T> mt) { ... } 
static M<R> Extend<A, R>(M<A> ma, Func<M<A>, R> func) { ... }

Extract is the "opposite" of Unit and Extend is the "opposite" of Bind.

Alternatively, you can also define a comonad with these operations:

static T Extract<T>(M<T> mt) { ... } 
static M<R> FMap<A, R>(M<A> ma, Func<A, R> func) { ... }
static M<M<T>> Duplicate<T>(M<T> mt) { ... }

Where Duplicate is the "opposite" of Join. Again, the two characterizations are equivalent; given one, you can build the other.

Clearly you cannot implement Extract only given Bind, Unit, FMap and Join, because none of those in any way return a T, and its a T that you need.

With either version of comonads, the problem you're running into is that the optional monad really isn't a comonad because there's no natural way to implement Extract if the monadic value is "missing".

Now, you can do what Nullable<T> does if you want. Nullable<T>.GetValueOrDefault() returns the value if there is one and default(T) if there isn't. That's probably the best you can do here if you want to make Optional into a comonad.

like image 55
Eric Lippert Avatar answered Sep 20 '22 06:09

Eric Lippert