無限リピートの幸福

Reactive Frameworkが、結構に無限リピートな感じなので、関連してC# Linqでどう書くにあった13日金曜日問題を今更書いてみた。n番煎じ。

// 今日から2013年12月31日までの、13日の金曜日とその総数を表示してください。
// 「今日」を無限リピートという方針で書いてみたりして(総数は省略)
// 利点はTodayを変数として外側に定義する必要が無くLinq内に閉じ込められる
// Toを求めるのに足したり引いたりする必要がなく自然に書ける、の二つかしらん
// 「まで」という問いに対してTakeWhileで解答するのは自然で良いと思う

Enumerable.Repeat(DateTime.Now, int.MaxValue)
    .Select((d, i) => d.AddDays(i))
    .TakeWhile(d => d.Year < 2014)
    .Where(d => (d.DayOfWeek == DayOfWeek.Friday) && (d.Day == 13))
    .ToList()
    .ForEach(d => Console.WriteLine(d.ToShortDateString()));

TakeWhileが好きです。問題文に対して、自然に解答出来るような気がするので。「今日から(Repeat)」「2013年12月31日までの(TakeWhile)」「13日の金曜日(Where)」。実に自然に記述できる。いやまあ、Repeatが直感的かというと結構微妙なところではありますが。Rangeでfrom,toのほうが自然だろ常識的に考えて、というのも確かなんですけど、Rangeだとtoを作るのに計算式が必要ってのが、ちょっと違うかな、と。

Pizza (programming language)のexampleにもあるような、Streamを始めとして何かを無限リピートしてTakeWhileで終了条件を設定、というのはパターンとして結構幅広く使える、と思う。ある種のデザインパターン。イディオムイディオム。参考リンクはC# 3.0 と while(true) と Iterator - NyaRuRuの日記この辺り。

例えばVS2010から搭載されるEnumerable.Zipや、あとCycleを定義してみる。

// この二つを混ぜ合わす(VS2010で搭載されるZip関数)
var seq1 = Enumerable.Range(1, 10);
var seq2 = Enumerable.Range(10, 10);
Enumerable.Repeat(new { e1 = seq1.GetEnumerator(), e2 = seq2.GetEnumerator() }, int.MaxValue)
    .TakeWhile(t => t.e1.MoveNext() && t.e2.MoveNext())
    .Select(t => t.e1.Current + t.e2.Current); // ここがZipのSelectorの部分
// foo,bar,hoge,foo,bar,hogeを無限に繰り返す
var elements = new[] { "foo", "bar", "hoge" };
var cycle = Enumerable.Repeat(elements, int.MaxValue).SelectMany(ar => ar);

Linqのお陰でかつてない勢いでint.MaxValueを使っているこの頃。Repeatも万能ですねえ。いやまあ、もう素直にAchiral使えよって話なんですが、標準メソッドのみで粘るのも面白くて。そういえばでついでなのでlinq.jsでもやってみた。

// JavaScriptはAddDaysがないので副作用全開でTodayを
// setHours(24)で翌日にしてしまう、という方針でやってみた

E.Repeat(new Date())
 .Do("$.setHours(24)")
 .TakeWhile("$.getFullYear() < 2014")
 .Where("$.getDay() == 5 && $.getDate() == 13")
 .ForEach("alert($)");

DoはReactive Frameworkにもありました。副作用を加えた上で素通しするメソッド。副作用は嫌なものです。汚いです。何が嫌かというと、動作を考えるのに見る範囲を広げなきゃいかんところかなあ。そしてLinqの何がいいかというと、見る範囲が物凄く限定される(ラムダ式一文だけを見ればいい)と思っている。だからLinq内でクロージャ(というか外部の変数をキャプチャして使う)もあんま好ましくないし、C#クエリ構文のletも好きじゃない。なるべくなら使いたくない。長文耐性なのは分かるけれど、カッコやインデントがなくてスマートだけれど、その分だけスコープが不明瞭になるという側面が否めない。まあ、letが必要なシチュエーションをSelectManyでやると、大抵はもっと奇怪になるのですけど。

んでまあ、この場合だとAddDaysのかわりにnew Date(year,month,day)で新しいのを作れば副作用なくSelectが使えるわけですが、ありきたりで面白くないと思ったので別な方向に走ってみた。というか、無限リピートは、無限リピートする何かに対して副作用全開で操作を加え続ける、という形の方が面白いというか実用的というか普通だとは思う。冒頭の例みたいなやつだと、別にRangeでよくね?って感じですし。Haskellじゃないんだから、潔癖症にならずに、副作用といかに楽しくお付き合いするかが大事なのですかね。

あ、ちなみに$は引数が一つの場合の省略記法です。こういった機能はScalaにもある。引数が一つのみの場合が大半なので、記述がグッと縮まるし、何よりも引数名を付ける必要がないのが嬉しい。C#でも使えるようになると嬉しいなあ、とずっと思ってるんですが中々どうして無理なんですかねえ、残念。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive