Edited at

LangExtの紹介 - シーケンスの生成

More than 5 years have passed since last update.

前回はシーケンスの概要を紹介しました。

今回は、シーケンスの生成について紹介します。


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.RepeatSystem.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());

どちらも用意する、というのもアリなのでしょうか。おとーふさんとも相談ですね。


次回

次回は、長さ取得系のメソッドについてです。