LoginSignup
7
7

More than 5 years have passed since last update.

C#でOption<T>型(改良)

Posted at

以前C#でOption<T>型を作成しました。

これはこれで非常に便利で多用していたのですが、使っていて1つ不満な点が出てきました。

それはOption<T>型の実体にメッセージを格納できないという点です。Option<T>型の用途の1つとして、失敗するかもしれない関数の返り値としての利用が挙げられますが、そのような場合には往々にして失敗(あるいは成功)の内容を表すメッセージも共に関数の返り値として返したくなるものです。C#では関数の返り値は1つまでなのでOption<T>型の値と共にメッセージも返したい場合は面倒なことになります。

それならば、メッセージも格納できるようなOption<T>型を作ると便利になりそうです。

別の方法として、Tuple<T1, T2>型を使うということも考えられますが、Tuple<T1, T2>型は中に何が入っているのかが分かりにくいという欠点があります。メッセージを格納できるOption<T>型を作ってしまった方が型名から中に何が入っているかが明確で分かりやすいと思います。

更に別の方法として、Either<L, R>型を作って使うということも考えられます。こちらの場合、Tuple<T1, T2>型を使うよりは分かりやすいと思いますが、失敗/成功のメッセージのみを返し、結果の値は返さないような関数を作りたいという場合もあるでしょう。このような場合にはEither<L, R>型を使うことはできません(空である(何も値を持たない)ことを表す型を別に定義すれば出来なくもないですが)。代わりにOption<String>のような型を使うことになるでしょうが、そうすると、失敗/成功のメッセージを返す関数でも、結果の値も返すかどうかによって、返り値の型がEither<L, R>やOption<T>型になって統一感がなくなります。この点から見ても、メッセージを格納できるOption<T>型を別に作ってしまった方が分かりやすいと思います。

ということで、普通のOption<T>型に加えて別にメッセージを格納できる特殊なOption<T>型を作成することにしました。

以前の記事ではOption<T>抽象クラスを作成していましたが、抽象クラスでは実装しにくいのでインターフェイスに変えました。普通のOption<T>型はIOption<T>インターフェイスを実装し、メッセージを格納できるOption<T>型はIOption<T>インターフェイスに加えてIOptionM<T>インターフェイスを実装することにします。

以下がコードです。インターフェイス、普通のOption<T>型None<T>Some<T>、メッセージ付きのOption<T>型NoneM<T>SomeM<T>、補助的な機能を提供する静的クラスから構成されます。コメントを書いたのでこれ以上詳しく説明する必要はないでしょう。

    //普通の随意型のためのインターフェイス
    public interface IOption<T>
    {
        //値
        T Value { get; }
        //値が存在しないか
        bool IsEmpty { get; }

        //値が存在する場合には値を返し、存在しない場合には既定値を返す
        T GetOrDefault(T def);
        //値が存在するかどうかによって別々の処理を実行する
        void Match(Action none = null, Action<T> some = null);
        //値が存在するかどうかによって別々の関数を実行し、返り値を返す
        S Match<S>(Func<S> none = null, Func<T, S> some = null);
        //現在の実体から新しい普通の随意型の実体を作成し、返す
        IOption<S> Bind<S>(Func<T, IOption<S>> f);
    }

    //メッセージ付きの随意型のためのインターフェイス
    public interface IOptionM<T> : IOption<T>
    {
        //メッセージ
        string Message { get; }
    }

    //値が存在しない普通の随意型
    public class None<T> : IOption<T>
    {
        //値
        public T Value
        {
            //存在しないので例外を投げる
            get { throw new Exception("not existed value"); }
        }

        //値が存在しないか
        public bool IsEmpty
        {
            //存在しないので真を返す
            get { return true; }
        }

        //値が存在する場合には値を返し、存在しない場合には既定値を返す
        public T GetOrDefault(T def)
        {
            //存在しないので既定値を返す
            return def;
        }

        //値が存在するかどうかによって別々の処理を実行する
        public void Match(Action none = null, Action<T> some = null)
        {
            //値が存在しない場合に実行されるよう指定された処理が渡された場合には処理を実行する
            //渡されなかった場合には何もしない
            if (none != null)
                none();
        }

        //値が存在するかどうかによって別々の関数を実行し、返り値を返す
        public S Match<S>(Func<S> none = null, Func<T, S> some = null)
        {
            //値が存在しない場合に実行されるよう指定された関数が渡された場合には関数を実行し、返り値を返す
            //渡されなかった場合には例外を投げる
            if (none != null)
                return none();
            else
                throw new Exception("none is null");
        }

        //現在の実体から新しい普通の随意型の実体を作成し、返す
        public IOption<S> Bind<S>(Func<T, IOption<S>> f)
        {
            //基になる値が存在しないので値が存在しない新しい普通の随意型を作成し、返す
            return Option.Return<S>();
        }
    }

    //値が存在する普通の随意型
    public class Some<T> : IOption<T>
    {
        //値を受け取って実体化する
        public Some(T _value)
        {
            value = _value;
        }

        //値
        private T value;
        public T Value
        {
            //存在するので値を返す
            get { return value; }
        }

        //値が存在しないか
        public bool IsEmpty
        {
            //存在するので偽を返す
            get { return false; }
        }

        //値が存在する場合には値を返し、存在しない場合には既定値を返す
        public T GetOrDefault(T def)
        {
            //存在するので値を返す
            return Value;
        }

        //値が存在するかどうかによって別々の処理を実行する
        public void Match(Action none = null, Action<T> some = null)
        {
            //値が存在する場合に実行されるよう指定された処理が渡された場合には処理を実行する
            //渡されなかった場合には何もしない
            if (some != null)
                some(Value);
        }

        //値が存在するかどうかによって別々の関数を実行し、返り値を返す
        public S Match<S>(Func<S> none = null, Func<T, S> some = null)
        {
            //値が存在する場合に実行されるよう指定された関数が渡された場合には関数を実行し、返り値を返す
            //渡されなかった場合には例外を投げる
            if (some != null)
                return some(Value);
            else
                throw new Exception("some is null");
        }

        //現在の実体から新しい普通の随意型の実体を作成し、返す
        public IOption<S> Bind<S>(Func<T, IOption<S>> f)
        {
            //基になる値が存在するので値を基に新しい普通の随意型を作成し、返す
            return f(Value);
        }
    }

    //値が存在しないメッセージ付きの随意型
    public sealed class NoneM<T> : None<T>, IOptionM<T>
    {
        //メッセージを受け取って実体化する
        public NoneM(string _message)
        {
            message = _message;
        }

        //メッセージ
        private string message;
        public string Message
        {
            get { return message; }
        }
    }

    //値が存在するメッセージ付きの随意型
    public sealed class SomeM<T> : Some<T>, IOptionM<T>
    {
        //値とメッセージを受け取って実体化する
        public SomeM(T _value, string _message) : base(_value)
        {
            message = _message;
        }

        //メッセージ
        private string message;
        public string Message
        {
            get { return message; }
        }
    }

    //随意型のための補助的な関数を格納するクラス
    public static class Option
    {
        //値を受け取って値が存在する普通の随意型を返す
        public static IOption<T> Return<T>(T value)
        {
            return new Some<T>(value);
        }

        //値が存在しない普通の随意型を返す
        public static IOption<T> Return<T>()
        {
            return new None<T>();
        }

        //値とメッセージを受け取って値が存在するメッセージ付きの随意型を返す
        public static IOptionM<T> ReturnM<T>(T value, string message)
        {
            return new SomeM<T>(value, message);
        }

        //値を受け取って値が存在するメッセージが空の文字列であるメッセージ付きの随意型を返す
        public static IOptionM<T> ReturnM<T>(T value)
        {
            return new SomeM<T>(value, string.Empty);
        }

        //メッセージを受け取って値が存在しないメッセージ付きの随意型を返す
        public static IOptionM<T> ReturnM<T>(string message)
        {
            return new NoneM<T>(message);
        }

        //値が存在しないメッセージが空の文字列であるメッセージ付きの随意型を返す
        public static IOptionM<T> ReturnM<T>()
        {
            return new NoneM<T>(string.Empty);
        }

        //普通の随意型の実体を受け取って新しい普通の随意型の実体を作成し、返す
        public static IOption<S> Bind<T, S>(IOption<T> m, Func<T, IOption<S>> f)
        {
            return m.Bind(f);
        }
    }

    //随意型に対する拡張関数
    public static class OptionExtension
    {
        //随意型用の関数合成(通常の関数合成の逆順)
        public static Func<T, IOption<R>> AndThen<T, S, R>(this Func<T, IOption<S>> f, Func<S, IOption<R>> g)
        {
            return x => f(x).Bind(g);
        }

        //任意の型の実体を随意型の実体に変換する
        public static IOption<T> ToOption<T>(this T self)
        {
            return Option.Return(self);
        }

        //随意型の実体から新しい随意型の実体を作成し、返す
        public static IOption<S> Map<T, S>(this IOption<T> self, Func<T, S> f)
        {
            return self.Bind((elem) => Option.Return(f(elem)));
        }

        //随意型の実体から新しい随意型の実体を作成し、返す
        public static IOption<S> SelectMany<T, S>(this IOption<T> self, Func<T, IOption<S>> f)
        {
            return self.Bind(f);
        }

        //随意型の実体から新しい随意型の実体を作成し、新しい随意型の実体から更に新しい随意型の実体を作成し、返す
        public static IOption<R> SelectMany<T, S, R>(this IOption<T> self, Func<T, IOption<S>> f, Func<T, S, R> g)
        {
            return self.Bind((x) => f(x).Bind((y) => g(x, y).ToOption()));
        }
    }
7
7
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7