5

option<`a`>Petricek book (Real World Functional Programming)の F# モナドの 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値がデフォルトでない場合、またはデフォルトを返す場合は、値を抽出する操作が必要です。

Mapとの組み合わせでこれが可能かどうか疑問に思ってBindいましたが、そうではないと思います。

F# のドキュメントに戻って、追加すべき他の便利な拡張メソッドについてのヒントを得ることができましたが、正確には必要なものではありませんでした。

私は自分のニーズを満たすためにこの関数を設計しました:

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>モナドの操作を定義するための参照または共通の関数パターンはありますか? 必要に応じて新しい操作を追加するのは正しいですか?

4

1 に答える 1

16

常に値を抽出できるモナドは通常comonadです。モナドにはメソッドがあることを知ってM<T>います (C# 構文で)

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

または、代わりに、モナドを作成することもできます

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

2 つの特性は同等です。ある特定の実装を他の実装で構築できます。

コモンドには操作があります

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 の「反対」です。繰り返しますが、2 つの特性は同等です。一方を指定すると、もう一方を構築できます。

明らかに、Bind、Unit、FMap、および Join を指定して Extract を実装することはできません。これは、これらのいずれも T を返さず、必要な T であるためです。

どちらのバージョンのコモナドでも、モナド値が「欠落」している場合に Extract を実装する自然な方法がないため、オプションのモナドが実際にはコモナドではないという問題が発生しています。

これで、必要に応じて何を行うことNullable<T>ができます。Nullable<T>.GetValueOrDefault()ある場合とない場合の値を返しますdefault(T)。Optional を comonad にしたい場合、これがおそらくここでできる最善の方法です。

于 2013-04-11T19:11:04.507 に答える