SG

F# で LINQ 入門 Skip SkipWhile

 気が付けば年開けてましたね。今年も F#!F#!していきたいと思います。

open System.Linq

let actors = ["ドラえもん"; "のび太"; "しずか"; "スネ夫"; "ジャイアン"]

let game player = 
    player
    |> Seq.iter (printfn "%s がゲームをしています")

// 悪いなのび太、このゲームは3人用なんだ
actors.Skip(2) |> game

// お前のものはおれのもの、おれのものはおれの物
actors.SkipWhile(fun a -> a <> "ジャイアン") |> game

 実行結果

> // 悪いなのび太、このゲームは3人用なんだ
actors.Skip(2) |> game;;
しずか がゲームをしています
スネ夫 がゲームをしています
ジャイアン がゲームをしています
val it : unit = ()
> // お前のものはおれのもの、おれのものはおれの物
actors.SkipWhile(fun a -> a <> "ジャイアン") |> game;;
ジャイアン がゲームをしています
val it : unit = ()
> 

 UNO で Skip されるときの得も言われぬ悲しみを F# で表現するのが今年の目標の1つです。

F# 3.0 の Query expressions

 この記事は F# Advent Calendar 2011 の13回目です。

 

はじめに

 今年の Advent Calendar 向けに、「F# で iPhone 向けゲームを作成する」という記事を書こうと奮闘していましたが、ゲーム(というより 3D)の開発知識を問われたり、残業の理に導かれたりして間に合わなくなってしまいました。最近、仕事の合間に LINQ の復習をしていたので、F# 3.0 からサポートされる予定の「Query expressions」についてゆるふわに書いておこうと思います。

注意

 Developer Preview の F# を使用しているため、将来サポートされる形式がこの通りとは限らないことを予めお伝えしておきます。間違ったことを書いて初心者*1に害を成す恐れがありますので、F# に詳しい人は随時指摘の準備をしておいてください。

Query expressions

 Query expressions(クエリ式) を利用することで、F# でも C#VB のようにキーワードを使用して LINQ によるデータアクセスを行うことが可能となります。

open System.Text.RegularExpressions

type bookData = { Title : string; Authors : string list }

(* F# おすすめ書籍。@wof_moriguchi さんの記事を参考にしました。ありがとうございます *)
let fSharpBooks = [{ Title = "実践F#"; Authors = ["荒井省三"; "いげ太"] }
                   { Title = "プログラミングF#"; Authors = ["Chris Smith"] }
                   { Title = "Beginning F#"; Authors = ["Robert Pickering"; "Don Syme"; "Chance Coble"] }
                   { Title = "Professional F# 2.0"; Authors = ["Ted Neward"; "Aaron Erickson"; "Talbott Crowell"; "Rick Minerich"] }
                   { Title = "Expert F# 2.0"; Authors = ["Don Syme"; "Adam Granicz"; "Antonio Cisternino"] }
                   { Title = "F# for Scientists"; Authors = ["Jon Harrop"; "Don Syme"] }
                   { Title = "Real-World Functional Programming: With Examples in F# and C#"; Authors = ["Tomas Petricek"; "Jon Skeet"] }
                  ]

let foreignBooks = 
    let isAlphabetAndNumber s = Regex.IsMatch(s, @"^[a-zA-Z0-9#.\s]+$", RegexOptions.Compiled)
    query {
        for book in fSharpBooks do
        where (book.Title |> isAlphabetAndNumber)
    }

foreignBooks
|> Seq.iter (fun book -> printfn "F# のお薦め洋書 %s" book.Title)


構文について

 F# のクエリ構文の LINQ は、

query { expression }

 となっています。seq や async のように、組込のコンピュテーション式として実装されています。query の実体は QueryBuilder というクラスのインスタンスです。
 QueryBuilder のメソッドを眺めてみると、Where、Select、All など、C# プログラマーVB プログラマー(俺俺)に馴染みの面々が揃っています。QueryBuilder で定義された関数によって動作を置き換えることにより、クエリ構文による記述をサポートしているようです。

メソッドについて

 上記のコードのとおり、C#VB では、クエリ構文の LINQ は「from 〜」で始まりますが、F# では「for 〜」で始まります。C#VB のクエリ構文では、スペースが異なる程度で名称に大きな差はありませんでした。しかし F# のクエリ構文は C#VB とは異なる単語が使用されているものがいくつか存在します。
 比較のため、一覧を作成してみました。

標準クエリ演算子 F# C# VB
All(Of TSource) all (…) 該当なし Aggregate … In … Into All (…)
Any exists (…) 該当なし Aggregate … In … Into Any()
Average averageBy (…) 該当なし Aggregate … In … Into Average()
Cast(Of TResult) 該当なし from int i in numbers From … As …
Count count 該当なし Aggregate … In … Into Count()
Distinct(Of TSource)(IEnumerable(Of TSource)) distinct 該当なし Distinct
GroupBy groupBy … into group … by または group … by … into … Group … By … Into …
GroupJoin(Of TOuter, TInner, TKey, TResult)(IEnumerable(Of TOuter), IEnumerable(Of TInner), Func(Of TOuter, TKey), Func(Of TInner, TKey), Func(Of TOuter, IEnumerable(Of TInner), TResult)) groupJoin (…) into join … in … on … equals … into … Group Join … In … On …
Join(Of TOuter, TInner, TKey, TResult)(IEnumerable(Of TOuter), IEnumerable(Of TInner), Func(Of TOuter, TKey), Func(Of TInner, TKey), Func(Of TOuter, TInner, TResult)) join (…) join … in … on … equals … From x In …, y In … Where x.a = b.a または Join … [As …]In … On …
LongCount 該当なし 該当なし Aggregate … In … Into LongCount()
Max maxBy 該当なし Aggregate … In … Into Max()
Min minBy 該当なし Aggregate … In … Into Min()
OrderBy(Of TSource, TKey)(IEnumerable(Of TSource), Func(Of TSource, TKey)) sortBy orderby Order By
OrderByDescending(Of TSource, TKey)(IEnumerable(Of TSource), Func(Of TSource, TKey)) sortByDescending orderby … descending Order By … Descending
Select select select Select
SelectMany 複数の for 複数の from 複数の From
Skip(Of TSource) skip 該当なし Skip
SkipWhile skipWhile (…) 該当なし Skip While
Sum sumBy 該当なし Aggregate … In … Into Sum()
Take(Of TSource) take 該当なし Take
TakeWhile takeWhile (…) 該当なし Take While
ThenBy(Of TSource, TKey)(IOrderedEnumerable(Of TSource), Func(Of TSource, TKey)) thenBy orderby …, … Order By …, … Descending
Where where (…) where Where

 調べてみるまで知らなかったのですが、C# には無いキーワードが VB には多く実装されていたのですね。VBラムダ式がアレすぎるせいでしょう。VB では標準クエリ演算子よりもクエリ構文を使用したほうが幸せになれるかもしれません。C# を採用したほうが幸せになれるかもしれません。F#をさい(ry
 F# だけのキーワードも。

標準クエリ演算子 F#
ElementAt nth
First head
FirstOrDefault headOrDefault
Last last
LastOrDefault lastOrDefault
Single exactlyOne
SingleOrDefault exactlyOneOrDefault

 名称が関数型らしい名前になっているような気がしますね。他に Nullable 用の構文が用意されています。F# ならでは。

多段 for

let fSharpEvangelist = 
    query {
        for book in fSharpBooks do
        for author in book.Authors do
        sortBy author
        select author
        distinct
    }

fSharpEvangelist
|> Seq.iter (fun name -> printfn "F# エヴァンジェリスト %s" name)

 C#VB で from を多段に出来るように、F# のクエリ構文でも for を多段にすることが出来ます。

let orenoShelf = [{ Title = "実践F#"; Authors = ["荒井省三"; "いげ太"] }
                  { Title = "プログラミングF#"; Authors = ["Chris Smith"] }
                 ]

let homu =
    query {
        for book  in fSharpBooks do
        for oreno in orenoShelf do
        where (book.Title = oreno.Title)
    }

 このときの homu の型は seq となります。select で射影しないと、タプルになるようです。

nth について

(* 見た感じは 4th ですが、当然添え字なので 5 番目の要素を取得します *)
let query3 =
    query {
        for book1 in fSharpBooks do
        nth 4
    }

query3 |> printfn "%A"

 はまる人はいないと思いますが、nth の始まりは 0 です。はい、誰もはまりませんね。配列を宣言するときの添え字が要素数じゃなくて添え字の最大数とかやめてほしいよね。いや dis っているのは空想上の言語ですよ?

F#にクエリ構文が増えると

 クエリ構文が増えるこということは、(自身を含む)読み手に伝えるための語彙が増えるということ。これにより、プログラマーが処理の意図を示すための選択肢も増えることになります。switch 文で書かれたコードは if 文でも書くことが出来ますが、文脈的には switch 文で書かれた方が読みやすい、というケースのように、クエリ構文で書かれた LINQ のほうが、標準クエリ演算子で書かれた LINQ や Seq モジュールよりも読みやすかったり、伝えたい意図を示すことが出来るケースが存在するのではないかと思います。多分、おそらく、きっと、maybe。逆もまた然りですが、コンテキストに合わせた選択が必要となってくるでしょう。

今後の課題

 需要があればモジュール、標準クエリ演算子、クエリ構文の対比を書いてみようと思います。
 F# の疑問を書くとこわい……じゃなかった優しいお兄ちゃんが答えてくれるらしいので。直接 F# と関係ないですが、VS11DP の fsi が文字化けするんですが、どうすればいいのでしょうか! 今回実行結果が画像なのはそのせいです。

*1:自分こそが初心者だとも

F# で LINQ 入門 Distinct

 入門と銘打ってますが、入門しているのはわたしです。Distinct メソッドでは、シーケンス内から重複した要素を取り除いたシーケンスを取得します。

open System.Linq

let cart = [ "TaPL", 6119;
             "SICP", 4830; 
             "TaPL", 6220;
             "SICP", 4830; 
             "TaPL", 6119;]

cart.Distinct()
|> Seq.iter (function | name, price -> printfn "%s を %d円で購入します" name price)

 実行結果

TaPL6119円で購入します
SICP4830円で購入します
TaPL6220円で購入します

val cart : (string * int) list =
  [("TaPL", 6119); ("SICP", 4830); ("TaPL", 6220); ("SICP", 4830);
   ("TaPL", 6119)]

 TaPL や SICP を読めるようなレベルになりたいですね。頑張ろう。まず英語が駄目駄目なんですけども。

F# で LINQ 入門 Where

 Where はシーケンスに対し抽出を行います。LINQ の中では最も使用されるメソッドの1つではないでしょうか。

open System.Linq

(* 年齢不詳の人物もいるため Option *)
type person = { name : string; age : Option<int> }

(* 公式より抜粋 http://www.guilty-crown.jp/ *)
let charactors = { name = "Shu";      age = Some(17) }
              :: { name = "Gai";      age = Some(17) } 
              :: { name = "Inori";    age = Some(16) } 
              :: { name = "Ayase";    age = Some(17) }
              :: { name = "Tsugumi";  age = Some(14) } 
              :: { name = "Shibungi"; age = Some(27) }
              :: { name = "Arugo";    age = Some(17) }
              :: { name = "Oogumo";   age = None }
              :: { name = "Fyu-Neru"; age = None }
              :: { name = "Hare";     age = Some(16) }
              :: { name = "Yahiro";   age = Some(17) }
              :: { name = "Souta";    age = Some(17) }
              :: { name = "Kanon";    age = Some(17) }
              :: { name = "Arisa";    age = Some(17) }
              :: { name = "Daryl";    age = Some(17) }
              :: []

(* 集以外で17歳以下 *)
charactors.Where(fun c -> c.name <> "Shu" && c.age.IsSome && c.age.Value <= 17)
|> Seq.iter (fun c -> printfn "%s" c.name)

実行結果

Gai
Inori
Ayase
Tsugumi
Arugo
Hare
Yahiro
Souta
Kanon
Arisa
Daryl

type person =
  {name: string;
   age: Option<int>;}
val charactors : person list =
  [{name = "Shu";
    age = Some 17;}; {name = "Gai";
                      age = Some 17;}; {name = "Inori";
                                        age = Some 16;}; {name = "Ayase";
                                                          age = Some 17;};
   {name = "Tsugumi";
    age = Some 14;}; {name = "Shibungi";
                      age = Some 27;}; {name = "Arugo";
                                        age = Some 17;}; {name = "Oogumo";
                                                          age = null;};
   {name = "Fyu-Neru";
    age = null;}; {name = "Hare";
                   age = Some 16;}; {name = "Yahiro";
                                     age = Some 17;}; {name = "Souta";
                                                       age = Some 17;};
   {name = "Kanon";
    age = Some 17;}; {name = "Arisa";
                      age = Some 17;}; {name = "Daryl";
                                        age = Some 17;}]

 ひょっとしたら集自身から取り出せるのかもしれないけれど。その展開も悪くは無い。

F# で LINQ 入門 Single SingleOrDefault

 職場でシングルベルを迎えそうなSGです。わたし、ひとりぼっち……

open System.Linq

let puellaMagi = ["Mami"]

puellaMagi.Single() |> printfn "%s"

let newPuellaMagi = "Madoka" :: puellaMagi

(*
ひとりぼっちじゃないのでエラー
newPuellaMagi.Single() |> printfn "%s"
newPuellaMagi.SingleOrDefault() |> printfn "%s"
*)

(* SingleOrDefault は空のシーケンスに対してのみ既定値を返す *)
[].SingleOrDefault() |> printfn "%s"

 実行結果

Mami


val puellaMagi : string list = ["Mami"]
val newPuellaMagi : string list = ["Madoka"; "Mami"]


 ところで、option のデフォルトって、None なのか、null なのかどっちなんだろう……と思って調べてみました。

let list = [Some("111")]
let empty = list.Tail
empty.SingleOrDefault()

 実行結果

val list : string option list = [Some "111"]
val empty : string option list = []

val it : string option = None

 もう None も恐くない

F# で LINQ 入門 First FirstOrDefault Last LastOrDefault

 始まりは肝心ですし終わりが良くなければ失敗だと思います。SGです。インデクサに 0 や hogeList.Count - 1 なんて書いていませんか? 書いてますね、わたしです。

open System.Linq

let firstKiss (taste : string[]) = 
    taste.First() |> printfn "初めてのキスは%sの味がした"

let lastKiss (taste : string[]) = 
    taste.Last() |> printfn "最後のキスは%sの味だった……"

(* 今回は配列 *)
let taste = [|"涙"; "苺"; "毒"|]

firstKiss taste
lastKiss taste

let empty = [||]

(* シーケンスに要素がないためエラー
firstKiss empty
lastKiss empty
*)

let firstKiss' (taste : string[]) =
    let message (taste : string[]) =
        match taste.FirstOrDefault() with
        | null -> "キスもしたことがない童貞諸君乙であります"
        | aji  -> sprintf "初めてのキスは%sの味がした" aji

    message taste |> printfn "%s"

let lastKiss' (taste : string[]) =
    let message (taste : string[]) =
        match taste.LastOrDefault() with
        | null -> "お前はいままでにしたキスの数を覚えているのか?"
        | aji  -> sprintf "最後のキスは%sの味だった……" aji

    message taste |> printfn "%s"

let parameters = [| taste; empty; |]

Array.iter firstKiss' parameters
Array.iter lastKiss'  parameters

 実行結果

初めてのキスは涙の味がした
最後のキスは毒の味だった……
初めてのキスは涙の味がした
キスもしたことがない童貞諸君乙であります
最後のキスは毒の味だった……
お前はいままでにしたキスの数を覚えているのか?

val firstKiss : string [] -> unit
val lastKiss : string [] -> unit
val taste : string [] = [|"涙"; "苺"; "毒"|]
val empty : 'a []
val firstKiss' : string [] -> unit
val lastKiss' : string [] -> unit
val parameters : string [] [] = [|[|"涙"; "苺"; "毒"|]; [||]|]

F# で LINQ 入門 ElementAt ElementAtOrDefault

 なかなか .NET Framework 2.0 を抜け出せないまま、LINQ なんて使わないし覚えなくていいや……と思っていたらいつの間にか .NET Framework も 4.5 がリリースされようとしているではありませんか。最近はテストメソッドで LINQ を嗜んでいますが、相変わらずプロジェクトは .NET Framework 2.0 のまま。世の中の動きに置いてかれないよう、LINQ のおさらいをしておくことにしました。折角なので F# で。List モジュールがあるのに F# で。

open System.Linq

(* 入部した順に並んでいるリスト。入部順はアニメ準拠 *)
let mizusawa = ["Chihaya"; "Taichi"; "Kanade"; "Tsutomu"; "Nikuman"]

(* n番目に入部した子を取得する *)
let nthMember n = 
    let n = n - 1
    mizusawa.ElementAt n

(* 肉まん君が表示される *)
5 |> nthMember |> printfn "%s"

(* 
   まだ入部していない子を表示しようとするとエラー 
6 |> nthMember |> printfn "%s"
*)

(* エラーではなくデフォルト値が欲しいときは ElementAtOrDefault を使用する *)
let nthMember' n = 
    let n = n - 1
    mizusawa.ElementAtOrDefault n

5 |> nthMember' |> printfn "%s"
6 |> nthMember' |> printfn "%s"

(* null はいやん……option を使おう *)
let nthMember'' n = 
    let n = n - 1
    match mizusawa.ElementAtOrDefault n with
    | null -> None
    | name -> Some(name)

5 |> nthMember'' |> printfn "%A"
6 |> nthMember'' |> printfn "%A"

 ElementAt メソッドは単体で使う機会は少ないと思います。
 LINQ のメソッドを適当に分けていたら 24 回分になってしまいましたが、流行の一人 Advent Calendar ではありません。明日は First と Last の予定。