Option的なものを作ろうとして絶望した

ものすごい久々にブログを書くのですが、あまり役に立たない情報です(汗

ここ半年ほど C# を使用するプロジェクトに携わっています。C# は 4.0 から?だと思うのですが、ジェネリクスの型パラメータに変位指定(in とか out とか)ができるようになっています。
共変が 、反変が と、型パラメータを使用できる場所が分かりやすい指定になっています(out = 戻り値の型として使用可能、in = 仮引数の型として使用可能)。
また、いつのころからか導入されていた LINQ もあるのでリスト操作を非常にやりやすくなっています。しかも基本的に遅延リスト(というか、IEnumerable といイテレータを取得可能なインタフェース)なのも、慣れると嬉しいです。

しかしながら、LINQ を使っていればいるほど、SelectMany(Scala でいうところの flatMap) を使っていればいるほど、Scala でいうところの Option が無いのが耐え難いです。Option が無いために、以下のようなコードを頻繁に書いています。

entities.Select(x => x.Foo).Where(x => x != null).Select(x => x.Bar);


ちがうっ!ちがうんだ!!null 値かどうかに着目したいんじゃないです!!


Foo プロパティの先の Bar プロパティの値を取得したいだけなのですが、プロパティの設定有無をチェックするために null かどうかを調べる必要があります。
もし、Option 型があれば Foo プロパティの型自体を Option[Foo] にすることや、あるいは null の場合は None を返すような ToOption 拡張メソッドを用意することで、よりシンプルに記述できます。以下のような感じです。

entities.SelectMany(x => x.Foo.ToOption().Select(y => y.Bar));


そして、つい先日ついに我慢できなくなり、Scala の Option 的なものを作ろうとしたのですが・・・

public interface IOption<out T> : IEnumerable<T>
{
}
public struct Some<T> : IOption<T>
{
  // 省略
}
public struct None<T> : IOption<T>
{
  // 省略
}

↑ここまでは問題ありませんでした。
ところで、C#LINQ には、SingleOrDefault というメソッドがあります。このメソッドは、要素が 1 件あればそれを、0 件ならデフォルト値を返すという振る舞いをします。しかし、デフォルト値は default(T) ときまっており、利用時に指定できません。この振る舞いが以外不便で、利用時に指定したかったので、IOption に GetOrElse を実装しようとしたのですが・・・

public interface IOption<out T> : IEnumerable<T>
{
  T GetOrElse(T value); // コンパイルエラー
}

T は として宣言しているため、メソッドの引数として使用することは出来ません。でも、出来ることならば共変としておきたいので、Scala のようにメソッドに型変数を追加して、下限境界を指定すればいいやと思いました。
Scala だったら以下みたいな感じです。

  def getOrElse[U>:T](value: U): U

・・・・・・・・C# では下限境界の指定が出来ない模様です orz
C# も 4.0 になって out とか in とか書けるのだから [U>:T] みたいな下限境界も指定できるだろうとおもっていたのですが、見つけられませんでした。C# で型パラメータに対する制約は where で書くのですが、書けるのは以下の 4 種類くらいなもので下限境界を指定する方法がありませんでした。

where T : <実際の型>
where T : U
where T : struct // 値型
where T : class // 参照型

というわけで、Option 的なものはいまだ作らず、where(x => x != null) と書いています orz

おまけ

ついでに、良く使う範囲での ScalaC# のリスト操作対応表。

Scala C#
map Select
flatMap SelectMany
filter Where
foldLeft Aggregate *1
foldRight なし
reduceLeft Aggregate *2
reduceRight なし
foreach なし
zip Zip *3

すぐに思いつくのは、この辺りぐらい。Skip とか Take、TakeWhile あたりもあったはず。あとは Single とか First とかかな。
collect が無いのはわりと不便に感じることが多い。C# には PartialFunction が無いのでしょうがないのだろうけど。あと、partition も無い気がする。これは作ってもいいんじゃないかなぁ?と思う。遅延リストにするのは難しいのかもだけど。


そうそう、遅延リスト。これについては C# の方が数段便利だと感じてる。遅延リスト・・・というか、IEnumerable を実装するのがものすごい簡単。基本はメソッドから値を返すときに yield return すれば、あとはよしなにやってくれるので、実装の手間がものすごい少ないです。しかも、遅延リストになる。
たとえば、Select だけれども、自分で実装するなら以下のようなコードでよい。

public static class EnumerableExtensions
{
  public static IEnumerable<U> Select<T, U>(this IEnumerable<T> src, Func<T, U> func)
  {
    foreach (var value in src)
      yield return func(value);
  }
}

これだけで、yield return の各戻り値を要素として取り出せる IEnumerable のインスタンスを返すことが出来る。
ちなみに、yield return は繰り返し構文の中でなくとも使える。例えば、以下だと 1, 2, 3 を要素とする IEnumerable が得られる。

public IEnumerable<int> GetValues()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

戻り値は IEnumerable でなくても IEnumerator でもOKだったような気もする。ちなみにどちらであっても、次の要素が必要となったときに、メソッドの実行が続行されるようです。なので、無限リストを作成するのも簡単にできると思います。

public IEnumerable<int> Start(int start)
{
  for(var i=start;; i++)
    yield return i;
}

Scala の場合、Stream をつかって、ほげほげしないといけないので、ちょっと面倒です。

追記

id:bleis-tift さんの指摘をうけて、対応表の Zip 他に注釈を追加しました。

*1:seedの引数あり

*2:seedの引数無し

*3:第二引数に Tuple.Create を指定