LoginSignup
2
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-05-22

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

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

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

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

次回

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1