平々毎々(アーカイブ)

はてなダイアリーのアーカイブです。

C#のジェネリクスで型クラスを真似る

id:Nagise さんのJavaジェネリクス記事を読みながら、C#ではどうかな、とあれこれ考えているわけです。

そんな時に、ジェネリクスのカリー化 - プログラマーの脳みその話を振られて。

真の高階型引数、つまり、型パラメータを取る型パラメータは実現できないです。が、型クラス的なものであればエミュレートできるらしい。

もちろんCLRにもILにも型クラス的なものなんかないわけで、オダスキー教授が言うところの「貧者の型クラス(pdf)」を使う、つまり型に適合するメソッド群をインスタンスとして渡してやるしか方法はないわけですが(ちなみにScalaでは、そのインスタンスを暗黙的に生成したり渡したりできる仕掛けがある)。

というわけで、最初に書いたのがこっちなんだけど…

https://gist.github.com/matarillo/11392331

(追記)ん?型クラス(もどきインスタンス)、new()制約をつけられるんじゃないの?

ということは?

public static class MonadEx
{
    public static Value<TMonad, TResult> SelectMany<TMonad, TSource, TCollection, TResult>(
        this Value<TMonad, TSource> source,
        Func<TSource, Value<TMonad, TCollection>> collectionSelector,
        Func<TSource, TCollection, TResult> resultSelector)
        where TMonad : Monad<TMonad>, new()
    {
        var monad = new TMonad();
        return
            monad.Bind(source, x=>
                monad.Bind(collectionSelector(x), y=>
                    monad.Unit(resultSelector(x,y))));
    }
}

まさか?

internal class Program
{
    private static void Main(string[] args)
    {
        var m =
            from x in Maybe.Instance.Unit(12)
            from y in Maybe.Instance.Unit(34)
            select x + y;
        var message =
            m.HasValue()
                ? m.Value().ToString()
                : "Nothing";
        Console.WriteLine(message);
    }
}

いけるじゃないか!!

(追記)new制約つけてnewしましたが、よーく考えると、Valueクラスの中に型クラスもどきインスタンスを持つようにするとか、あるいはタプルで型クラスインスタンスValueクラスのインスタンスをまとめて扱うようにする方法もありますね。そしたら型クラスもどきインスタンスを使いたい側でnewしなくても、Valueと一緒に受け取れるはず。

https://gist.github.com/matarillo/11393891

xuweiさんにもコメントもらいたいなあ。