前回はシーケンスの概要を紹介しました。
今回は、シーケンスの生成について紹介します。
using
例に出すLangExtを使ったコードは、System名前空間とLangExt名前空間がusingされているものとします。
シーケンスの生成方法
シーケンスを生成する方法はいくつかあります。
- Enumerable.ToSeq
- Seq.Empty / Seq.Singleton
- Seq.Create / Create.Seq
- Seq.Init / Seq.InitInfinite
- Seq.Repeat / Seq.RepeatInfinite
- Seq.Unfold
他にもありますが、代表的には上に挙げた方法で生成することになります。
今回は、Seq.Unfold
以外を紹介します。
Seq.Unfold
は、Optionの紹介が終わってからか、Seq.Fold
を説明するときに紹介します。
Enumerable.ToSeq - IEnumerable[T]を変換
Enumerable.ToSeq
メソッドは、IEnumerable[T]
をISeq[T]
に変換するメソッドです。
IEnumerable[T]
に対する拡張メソッドとして定義されているため、
var seq = new[] { 1, 2, 3 }.ToSeq();
のように呼び出せます。
他のライブラリ(標準ライブラリ含む)からLangExtを採用したAPI側へデータを渡したいときに使います。
Seq.Empty / Seq.Singleton - 0 / 1要素のシーケンスを生成
Seq.Empty
はいいでしょう。System.Linq.Enumerable.Empty
に相当します。
Seq.Singleton
は、指定した要素を1つ含むシーケンスを生成します。
標準クエリ演算子では、
// using System.Linq;されているとする
var empty1 = Enumerable.Repeat(x, 1);
// もしくは、
var empty2 = new[] { x };
のように記述します。
前者はすぐに頭に入ってきませんし、少し長いです(補完を活用しても、「Enume.Rep」)。
後者は短いですが、記号が打ちにくい並びで並んでいるためか何なのか、一発でちゃんと打てません(個人的な問題でごめんなさい)。
これに対して、LangExtでは
var empty = Seq.Singleton(x);
と、それなりに書きやすく(Seq.Siまでで補完が効く)、Repeat(x, 1)
よりもより意図を素直に表せます。
また、「Tを受け取ってISeq[T]を返す関数」を受け取る高階関数があった際に、そのTの値一つのみを含むシーケンスでいい場合は以下のように書けます。
// void Something(Func<int, ISeq<int>> f); という関数があったとして
Something(Seq.Singleton);
// Repeatの場合
Something(x => Seq.Repeat(x, 1));
// 配列リテラルの場合(fがISeq<int>ではなく、IEnumerable<int>を返す関数だった場合
Something(x => new[] { x });
Seq.Singleton
以外は、ラムダ式が必要になってしまいます。
ちなみに、Singletonという名前はF#由来です(参考: Seq.singleton)。
Seq.Create / Create.Seq
LangExtでは、「モジュール名.Create」を提供している場合に、「Create.型名」という関数を提供しています。
モジュール名のルールはありますが、いちいち覚えるのは面倒です。
そこで、(LangExt内で)統一的に使える「Create」モジュールを提供しています。
Seq.Create / Create.Seqは可変長引数を取る関数として定義されており、渡した要素を含むシーケンスを生成します。
var seq = Create.Seq(10, 20, 30);
配列を受け取ってシーケンスを返す関数を受け取る関数があった場合に、何もせずにシーケンスに変換したい場合は以下のように書けます。
// void Something(Func<int[], ISeq<int>> f); という関数があったとして
Something(Create.Seq);
Seq.Init / Seq.InitInfinite
Seq.Init
は、要素数と関数を指定してシーケンスを生成し、Seq.InitInfinite
は関数のみを指定してシーケンスを生成する関数です。
Infiniteという語に反して、int.MaxValue
個の要素までしか正常に扱えないことに注意してください。
この2つの関数は、非常に便利ですが、標準クエリ演算子に対応するものはありません。
例えば「2の倍数の数列の先頭10要素がほしい」と思った場合、標準クエリ演算子では以下のように書くでしょう。
// System.Linq名前空間がusingされているとする
var res = Enumerable.Range(0, int.MaxValue).Where(i => i % 2 == 0).Take(10);
LangExtでは、Seq.Init
を使って、以下のように記述できます。
var res = Seq.Init(10, i => i % 2 == 0); // iには先頭から何番目の要素かが渡されてくる
LangExtを採用しないとしても、この関数は自分の道具箱に入れておくべきでしょう。
ちなみに、この2つの関数はF#由来です(参考: Seq.init / Seq.initInfinite)。
Seq.Repeat / Seq.RepeatInfinite
Seq.Repeat
は、要素数と要素を指定してシーケンスを生成し、Seq.RepeatInfinite
は要素のみを指定してシーケンスを生成する関数です。
Infiniteという語に反して、int.MaxValue
個の要素までしか正常に扱えないことに注意してください。
Seq.Repeat
はSystem.Linq.Enumerable.Repeat
に相当します。
Seq.RepeatInfinite
は、要素数を指定しないバージョンなので説明はいいでしょう。
この関数がない場合、
// System.Linq名前空間がusingされているとする
var seq = Enumerable.Range(0, int.MaxValue).Select(_ => x);
のように書く必要があります。
標準クエリ演算子に存在して、LangExtに存在しない生成関数
DefaultIfEmpty
DefaultIfEmpty
は簡単にnull
を持ち込むので、そのまま提供するつもりはありませんでした。
かと言っていい案があるわけではなかったので、2.0では実装しませんでした。
LangExt2.0リリース直前にUnsafe
名前空間を導入したときに、その中に含めようかとも思ったのですが、
あくまでもこの名前空間は「例外を投げうる」という関数の置き場所に限定したかったのです。
今のところ、Null
名前空間を導入し、そちらに入れることも考えていますが、まだ決定ではありません(その場合、構造体に対してはT?
を返すようにして、NullIfEmptyとでも名前を変えましょうか)。
Range
Range
は、第二引数が要素数なのかシーケンスの長さなのかがわかりにくいため、提供しません。
その代りに、Range型を導入予定です。
// Range型による実現
public struct Range
{
...
public ISeq<int> ToSeq() { ... }
}
Range
を提供し、第二引数は幽霊型にする、という案もたった今思いついたので、もしかするとそうなるかもしれません。
// 幽霊型による実現
public interface IUnit {}
public struct IntWithUnit<TUnit> where TUnit : IUnit { ... }
public struct Length : IUnit { ... }
public struct Position : IUnit { ... }
public static class LengthModule
{
public static IntWithUnit<Length> Len(this int length) { ... }
}
public static class PositionModule
{
public static IntWithUnit<Position> Pos(this int position) { ... }
}
public static class Seq
{
public static ISeq<int> Range(int start, IntWithUnit<Length> len) { ... }
public static ISeq<int> Range(int start, IntWithUnit<Position> end) { ... }
}
var seq = Seq.Range(0, 10.Len());
どちらも用意する、というのもアリなのでしょうか。おとーふさんとも相談ですね。
次回
次回は、長さ取得系のメソッドについてです。