选项<t> monad超出Bind和Map </t>的标准操作

时间:2013-04-11 17:34:56

标签: c# functional-programming monads

我正在使用来自Petricek书籍(真实世界函数式编程)的F#option<`a`> monad的C#实现:

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>();
    }
}

现在我需要一个操作来提取值,如果它不是None或返回默认值。

我想知道使用MapBind的组合是否可行,但我认为不是。

所以我回到F# documentation,它给了我一些其他有用的扩展方法的提示,但不完全是我需要的。

我设计了这个功能来满足我的需求:

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;
}

所以为了不重新发明轮子,问题是:是否有一个参考或常用的功能模式来定义Option<T> monad上的操作? 是否正确添加需要的新操作?

1 个答案:

答案 0 :(得分:15)

您可以随时提取值的monad通常是 comonad 。你知道monad M<T>有方法(用C#语法)

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

或者,你也可以用

制作一个monad
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) { ... }

这两个特征是等价的;你可以构建另一个给定的实现。

comonad有操作

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

Extract是Unit的“反面”,而Extend是Bind的“反面”。

或者,您也可以使用以下操作定义comonad:

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) { ... }

其中Duplicate是Join的“反面”。同样,这两个特征是等价的;给定一个,你可以建立另一个。

显然,只有给定Bind,Unit,FMap和Join才能实现Extract,因为这些都不会以任何方式返回T ,而是它需要的T。

使用任一版本的comonads,你遇到的问题是可选的monad真的不是comonad,因为如果monadic值“缺失”,没有自然的方法来实现Extract。

现在,如果需要,您可以执行Nullable<T>所做的事情。如果有值,则Nullable<T>.GetValueOrDefault()会返回值,如果没有则返回default(T)。如果你想将Optional变成comonad,这可能是你在这里做的最好的。