かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

手軽なスクリプト言語としてのF# その24「シーケンス」

今日はシーケンスです。シーケンスとはListみたいなもんです。ただ、Listとは違う特徴を持ってるのでしっかりそこらへんをおさえましょう。

使ってみよう

とりあえず使ってみないことにはどんなものかわかりません。シーケンスを生成して値を表示するだけのプログラムを書いてみます。

// 1〜5のシーケンス
let s = seq { 1..5 }

for i in s do printfn "%d" i
printfn "-----------------------"
// 1〜5を2乗したもの
let s2 = seq { for i in 1..5 do yield i * i }
for i in s2 do printfn "%d" i

実行すると以下のような結果になります。

1
2
3
4
5
-----------------------
1
4
9
16
25

とりあえず、seq { なにか }のような形でシーケンスが作れてyield 値とやることでシーケンスの要素を返すような形です。ということなので、以下のような実装でもOKです。

let s = seq {
    printfn "Generate One"
    yield "One"
    printfn "Generate Two"
    yield "Two"
    printfn "Generate Three"
    yield "Three"
}

for i in s do
    printfn "%s" i
for i in s do
    printfn "%s" i

実行すると以下のような結果になります。

Generate One
One
Generate Two
Two
Generate Three
Three
Generate One
One
Generate Two
Two
Generate Three
Three

この結果をよくみるとシーケンスの特徴が見えてきます。シーケンスの返す値は、シーケンスを作成したときではなく、シーケンスから値を取り出す時に作られています。シーケンスを作成したタイミングで値が生成されている場合は以下のような実行結果になるはずですから

Generate One
Generate Two
Generate Three
One
Two
Three
One
Two
Three

しかも、2回ループを回したら2回値の生成処理が走ってることがわかります。これも特徴ですね。
さて、yieldで値を返せることがわかりました、あと、値が必要になったタイミングで値を作る処理が走ることもわかりました。次は、もう1つの値の返し方のyield!の動きを見てみようと思います。

let s = seq {
    yield 0
    yield! [1;2;3]
    yield 4
}

for i in s do
    printfn "%d" i

これを実行すると以下のようになります。

0
1
2
3
4

yieldが引数に渡された値をそのまま返すのに対してyield!は引数に渡された集合をバラしてからシーケンスの要素として返します。

因みにシーケンスは・・・

F#のシーケンスですが、結局のところ.NETのIEnumerableだったりします。

シーケンスの便利メソッド

因みにシーケンスを操作するための色々な便利な関数達がSeqモジュールに定義されています。MSDNを見るのがいいです。

ちょっとだけ関数を使ってみます。

let s = seq { 1..100 }

s |>
    // 偶数だけ抽出して
    Seq.filter (fun i -> i % 2 = 0) |>
    // 合計をとる
    Seq.sum |>
    // 印字
    printfn "%d"

こんな風にパイプ演算子で繋いで色々やることで、LINQやってるみたいにサクサクと集合の処理を行うことが出来ます。
実行すると以下のようになります。

2550


あと、値が必要になったタイミングで値を取得するという特徴から無限のリストを扱うことが出来ます。

// 永遠に0,1,2,3,4...と値を返すシーケンス
let s = seq { 
    let i = ref 0
    while true do 
        yield !i
        i := !i + 1
}

s |>
    // 最初の10個を飛ばして
    Seq.skip 10 |>
    // そのあとの10個を取り出して
    Seq.take 10 |>
    // 印字する
    Seq.iter (printfn "%d")

実行すると、ちゃんと値が表示されます。

10
11
12
13
14
15
16
17
18
19

いい感じですね。

過去記事