100までの素数を求めるプログラム、その後

http://d.hatena.ne.jp/kurip/20060219#1140316336
の続き.

Visual C# 2008 Express Editionのインストール記念で再びチャレンジしてみました.
前回の最終形は以下の通りでした.

class Program
{
  static List<int> GetPrimes(int maxNum)
  {
    List<int> primeList = new List<int>();
    for (int i = 2; i <= maxNum; i++)
    {
      if (primeList.TrueForAll(delegate(int j) { return i % j != 0; }))
      {
        primeList.Add(i);
      }
    }
    return primeList;
  }

  static void Main(string[] args)
  {
    foreach (int prime in GetPrimes(100))
    {
      Console.Write("{0},", prime);
    }
    Console.WriteLine("");
  }
}

いまだったらどうしようかな?GetPrimesメソッドの部分は別クラスにして,イテレータを実装しようかしら?
TrueForAllはAllメソッドにして,匿名型(var)も使ってと.こんな感じかな?

internal class PrimeList : IEnumerable<int>
{
  int count;
  internal PrimeList(int count) { this.count = count; }

  public IEnumerator<int> GetEnumerator()
  {
    var primes = new List<int>();

    for (var i = 2; i<count; i++)
    {
      if (primes.All(j => i % j != 0))
      {
        primes.Add(i);
        yield return i;
      }
    }
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return ((IEnumerable<int>)this).GetEnumerator();
  }
}

class Program
{
  static void Main(string[] args)
  {
    foreach(var prime in new PrimeList(100))
    {
      Console.Write("{0},", prime);
    }
    Console.WriteLine();
  }
}

さて,ここからどうしましょうか?
よくC# 3.0は関数型言語っぽくなったなんて言われますので,ちょっとそれっぽく変化させてみましょうか?
とはいえ自分の関数型言語の知識は,

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

↑を斜め読みしただけなので,イマイチだったらすみません.

まずはfor文を再帰にしてみます.

  public IEnumerator<int> GetEnumerator()
  {
    var primes = new List<int>();

    Action<int> func = i =>
    {
      if (primes.All(j => i % j != 0))
      {
        primes.Add(i);
      }
    };

    int x = 1;
    while (true)
    {
      func(++x);
      if (x >= count)
        break;
    }

    foreach (var prime in primes) { yield return prime; }
  }
}

yieldはラムダ式の中には入れられないっぽいので,仕方なく素数リストを作ったあとで,foreachで回すことにしました.それにしても,激しくイマイチですね(^^;;
ところでHaskell本を読むと,Haskellにはif文はあるにはあるんですが,ちょっと考え方が違うみたいで,どちらかというと3項演算子に近いものらしいです.Action funcの中のif文を3項演算子に置き換えるために,引数と戻り値にListを加え,ifの条件を満たしていたらAddしたものを返し,満たしていなかったら引数をそのまま返すことにしましょう.

    Func<List<int>, int, List<int>> addPrime = (list, i) => list.All(j => i % j != 0) ? list.Add(i) : list;

残念!!list.Addの戻り値はvoidでした.仕方ないのでヘルパー関数を作りましょう.

    Func<List<int>, int, List<int>> addList = (list, i) => { list.Add(i); return list; };
    Func<List<int>, int, List<int>> addPrime = (list, i) => list.All(j => i % j != 0) ? addList(list, i) : list;

これらを使えば,再帰の部分もなんとかなりそうです.

    Func<List<int>, int, List<int>> rec = (list, i) => i < count ? rec(addPrime(list, i), ++i) : list;

またまた残念!!これだと何故かコンパイルエラーになってしまいます.以下のようにすればOK(なんでだろう?)

    Func<List<int>, int, List<int>> rec = null;
    rec = (list, i) => i < count ? rec(addPrime(list, i), ++i) : list;

そんなわけで最終的には,以下のようになりました.

  public IEnumerator<int> GetEnumerator()
  {
    Func<List<int>, int, List<int>> addList = (list, i) => { list.Add(i); return list; };
    Func<List<int>, int, List<int>> addPrime = (list, i) => list.All(j => i % j != 0) ? addList(list, i) : list;
    Func<List<int>, int, List<int>> rec = null;
    rec = (list, i) => i < count ? rec(addPrime(list, i), ++i) : list;

    var primes = rec(new List<int>(), 2);
    foreach (var prime in primes) { yield return prime; }
  }

いまの自分にはこれが精いっぱい.また数年後つづき書くかも(^^;;