演算のできる Type

仕事で何かを調べるのに Google を利用したりしてると、時々 Hatena::Diary がひっかかると、ちょっと優先してみてしまったりします。
別に Hatena::Diary の日記の質がよくて解決に至るヒントが乗っている場合が高いわけではなく……どちかといえば、その時の検索目的に対してはぜんぜん必要ない情報が多かったりしますが、目につく話題がある率は高いような気がします。

というわけで、仕事中に目にとまったのは、id:akiramei:20040723#p1 だったりするんですが。

なぜ Typed-parameter に対して演算したいのか?

なぜっていわれても、まあ普通はやりたいわなー(苦笑
特に int や double みたいな型は非常によくパラメタライズされるし、.NET 2.0 だと値型はアセンブリ生成時に実装が実体化されるから、うまいことやればコンパイル時やJIT時にかなりの最適化がかけられるというのも魅力的かも知れない?

何も考えずに

とりあえず考えなしに、素直に制約インターフェスを書いてみる。

/// Four Rule Of Arithmetic -able
public interface IFROAble
{
  T Add(T left, T right);
  T Substract(T left, T right);
  T Multiply(T left, T right);
  T Divide(T left, T right);
  T Negate(T value);
}

使ってみる。

static T Sum(params T[] values) where T : IFROAble
{
  if (values == null) throw new ArgumentNullException("values");

  T sum = default(T);
  foreach (T value in values)
  {
    sum = sum.Add(sum, value);
  }

  return sum;
}

あれ、T を演算対象の型としてみると、この制約に出てくる T は実装を持った型じゃないといけないから T では耐えられない。
と、ここで数十秒考えたが、素直に

 static T Sum(params T[] values) where I : IFROAble
{
  if (values == null) throw new ArgumentNullException("values");

  I fra = default(I);
  T sum = default(T);
  foreach (T value in values)
  {
    sum = fra.Add(sum, value);
  }

  return sum;
}

と Type と Implementation を別個で受け取るように改めた。
あまり省略しまくった名称は好きじゃないのだが、頭文字をとって T, I とした。

そして使用する。

class Int32FROAImplements : IFROAble
{
  public int Add(int left, int right)
  {
    return left + right;
  }

  public int Substract(int left, int right)
  {
    return left - right;
  }
  // :
  // :たっぷり略
  // :
}

public static void Main()
{
  Console.WriteLine(Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
  Console.WriteLine(
      Sum(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1));
}

とりあえず問題なく動作するものができた。

書き直し

実装は固定なのだから、プリミティブ型など多用される型の実装を毎回行う必要はなく

public struct FROAPrimitive : 
    IFROAble, IFROAble, IFROAble, IFROAble, IFROAble,
    IFROAble, IFROAble, IFROAble, IFROAble,
    IFROAble, IFROAble, IFROAble, IFROAble
{
  #region Add()
  public byte Add(byte left, byte right) { return left + right; }
  public char Add(char left, char right) { return left + right; }
  public short Add(short left, short right) { return left + right; }
  public int Add(int left, int right) { return left + right; }
  // :
  // :
}

という構造体を用意しておけば問題ないし、実際に使う時にも、

public class xxx where I : IFROAble
{
  private I fra = default(I);

  public T something(T some, T value)
  {
    // :
  }
}

と、実体はクラスで1つあれば十分だろうし、後から他の型を演算しない前提のプリミティブ型だけに限定したクラスならばパラメータ型に I も取らなくていいよね〜ってところでしょうか。
あとは名前がいやな感じなので inteface IMath と struct Math に改名し、だらだら実装を足しておしまい!寝る!