2008年4月11日金曜日

C#で関数型: Unfold

関数型言語にはUnfoldという関数が用意されていることが多い。Schemeだとこんな感じ。
(unfold p f g seed tail-gen) ==
   (if (p seed)
       (tail-gen seed)
       (cons (f seed)
             (unfold p f g (g seed))))
これをC#に直訳する。ただし、tail-genは無視する(optionalなので)。
public static IEnumerable<U> Unfold<T, U>
(
  Predicate<T> p,
  Func<T, U> f,
  Func<T, T> g,
  T seed
)
{
  if (p(seed))
    return Enumerable.Empty<U>();
  else
    return Cons
    (
      f(seed),
      Unfold(p, f, g, g(seed))
    );
}
 
public static IEnumerable<T> Cons<T>
(
  T car,
  IEnumerable<T> cdr
)
{
  yield return car;
  foreach (T element in cdr)
    yield return element;
}
これって、結局こういうこと。(そして、C#には末尾最適化がないのだから、必ずこう書くべきだ。)
public static IEnumerable<U> Unfold<T, U>
(
   Predicate<T> p,
   Func<T, U> f,
   Func<T, T> g,
   T seed
)
{
   for (T i = seed; !p(i); i = g(i))
      yield return f(i);
}
だったら、パラメータの順番を変えたほうが、Unfoldを呼び出すときにIntelliSenseが効いてうれしい。
public static IEnumerable<U> Unfold<T, U>
(
   T initial,
   Predicate<T> until,
   Func<T, T> next,
   Func<T, U> convert
)
{
   for (T i = initial; !until(i); i = next(i))
      yield return convert(i);
}
C#にわざわざUnfoldを用意する必然性はあるのだろうか。自分でも考えてみる。
そうだな、匿名メソッドやラムダ式ではyieldが使えないけど、それで困るようなシチュエーションならFoldとかUnfoldとかその類のメソッドがあると便利。
でもより重要なのは、きっとDLinqやPLinq(んー、でもUnfoldを並列処理するのは難しいか)みたいな世界に簡単に対応できるというポテンシャルなんだろう。そういう意味では、今現在での意味は薄いのか。

0 件のコメント:

コメントを投稿