C#でSTL(無駄な足掻きとも言う)

C#にもSTLやboostっぽいライブラリが欲しいという話題がありましたので、C#で実装するとどんな感じになるか、ちょっと実験。型パラメータに対する演算については私の日記よりも、ladybugさんの方がスマートなので参考にさせて貰いました。

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using Functional;

#endregion

namespace Functional
{
    // 算術演算インタフェース
    public interface IArithmetic<T>
    {
        T Add(T x, T y);
        T Subtract(T x, T y);
        T Multiply(T x, T y);
        T Divide(T x, T y);
        T Negate(T x);
        T Unit();
        int Compare(T x, T y);
    }

    // デフォルトの実装
    public struct Arithmetic : IArithmetic<int>, IArithmetic<uint>,
        IArithmetic<long>, IArithmetic<ulong>, 
        IArithmetic<float>, IArithmetic<double>
    {
        #region int
        public int Add(int x, int y) { return x + y; }
        public int Subtract(int x, int y) { return x - y; }
        public int Multiply(int x, int y) { return x * y; }
        public int Divide(int x, int y) { return x / y; }
        public int Negate(int x) { return -x; }
        int IArithmetic<int>.Unit() { return 1; }
        public int Compare(int x, int y) { 
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion

        #region uint
        public uint Add(uint x, uint y) { return x + y; }
        public uint Subtract(uint x, uint y) { return x - y; }
        public uint Multiply(uint x, uint y) { return x * y; }
        public uint Divide(uint x, uint y) { return x / y; }
        public uint Negate(uint x) { return 0 - x; }
        uint IArithmetic<uint>.Unit() { return 1; }
        public int Compare(uint x, uint y)
        {
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion

        #region long
        public long Add(long x, long y) { return x + y; }
        public long Subtract(long x, long y) { return x - y; }
        public long Multiply(long x, long y) { return x * y; }
        public long Divide(long x, long y) { return x / y; }
        public long Negate(long x) { return -x; }
        long IArithmetic<long>.Unit() { return 1; }
        public int Compare(long x, long y)
        {
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion

        #region ulong
        public ulong Add(ulong x, ulong y) { return x + y; }
        public ulong Subtract(ulong x, ulong y) { return x - y; }
        public ulong Multiply(ulong x, ulong y) { return x * y; }
        public ulong Divide(ulong x, ulong y) { return x / y; }
        public ulong Negate(ulong x) { return 0 - x; }
        ulong IArithmetic<ulong>.Unit() { return 1; }
        public int Compare(ulong x, ulong y)
        {
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion

        #region float
        public float Add(float x, float y) { return x + y; }
        public float Subtract(float x, float y) { return x - y; }
        public float Multiply(float x, float y) { return x * y; }
        public float Divide(float x, float y) { return x / y; }
        public float Negate(float x) { return -x; }
        float IArithmetic<float>.Unit() { return 1.0f; }
        public int Compare(float x, float y)
        {
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion

        #region double
        public double Add(double x, double y) { return x + y; }
        public double Subtract(double x, double y) { return x - y; }
        public double Multiply(double x, double y) { return x * y; }
        public double Divide(double x, double y) { return x / y; }
        public double Negate(double x) { return -x; }
        double IArithmetic<double>.Unit() { return 1.0; }
        public int Compare(double x, double y)
        {
            if (x == y) return 0;
            return (x < y) ? -1 : 1;
        }
        #endregion
    }

    public delegate R UnaryFunction<A, R>(A arg);
    public delegate R BinaryFunction<A1, A2, R>(A1 arg1, A2 arg2);

    // Binder
    public static class Binder
    {
        public static UnaryFunction<A2,R> 
            Bind1st<A1, A2, R>(BinaryFunction<A1, A2, R> pr, A1 arg1)
        {
            return delegate(A2 arg2)
            {
                return pr(arg1, arg2);
            };
        }

        public static UnaryFunction<A1, R>
            Bind2nd<A1, A2, R>(BinaryFunction<A1, A2, R> pr, A2 arg2)
        {
            return delegate(A1 arg1)
            {
                return pr(arg1, arg2);
            };
        }
    }
}

class Program
{
    // [begin, end)を返す
    static IEnumerable<T> Range<T, M>(T begin, T end, T step) 
        where M : IArithmetic<T>
    {
        M m = default(M);
        for (T t = begin; m.Compare(t, end) < 0; 
            t = m.Add(t, m.Multiply(m.Unit(),step))) 
            yield return t;
    }

    static void Main(string[] args)
    {
        Arithmetic arithmetic = new Arithmetic();

        // 第一引数を7に固定
        UnaryFunction<int, int> add7 = 
            Binder.Bind1st<int, int, int>(arithmetic.Add, 7);
        // [0, 10)に7を足す
        foreach(int n in Range<int, Arithmetic>(0, 10, 1))
            Console.Write("{0},", add7(n));
        Console.WriteLine();

        // 第二引数を1.05に固定
        UnaryFunction<double, double> tax = 
            Binder.Bind2nd<double, double, double>(arithmetic.Multiply, 1.05);
        // 100, 200,...900に1.05を掛ける
        foreach (double n in Range<int, Arithmetic>(100, 1000, 100))
            Console.Write("{0},", tax(n));
        Console.WriteLine();

        Console.ReadLine();
    }
}
/* 結果
7,8,9,10,11,12,13,14,15,16,
105,210,315,420,525,630,735,840,945,
 */

結構、ウンザリするかも。(^^;

で、やっていてぶつかった問題。まずは、byteやshortをサポートしていませんが、これは面倒になって省略した訳ではありません。

byte Add(byte x, byte x) { return x + y; }

byte型でAddを実装すると戻り値がintになるんですよね。キャストすればコンパイルは通りますが、それで本当にいいのか疑問です。まともにやろうとすると引数の型と戻り値の型を分ける必要があります。そうすると使い勝手が悪いので、byteやshortはざっくり省略しました。次に定数との演算です。例えば、

T t default(T);
t++;

こんなコードを書こうとすると++演算子が使えないので、コンパイルが通りません。仕方がないので、

T t = default(T);
t = m.Add(t, 1);

こうやって書き直しました。でもやっぱり通りません。T型のtとint型の1を引数に取るメソッドが無いからです。つまりT型の定数を取得する方法が無ければなりません。上記の例ではUnitというT型での1を返すメソッドを用意しました。昔の日記では、

T Value(int t);

int型を特別扱いして、int型をT型に変換するメソッドを用意しています。このように非常に面倒です。しかも算術演算が全部インタフェース経由のメソッド呼び出しなのでパフォーマンスも全く期待できません。structなので最適化されるみたい。と言うわけで、C#STLやboostは厳しいなぁ、という結論に至った訳なんですが、実は上手い方法があったりしないでしょうか?(^^;