F# 3.0 の Query expressions
この記事は F# Advent Calendar 2011 の13回目です。
- ←前回 [twitter:@furuya02] さん「F#によるパケットモニタの作成(WinPcap)」
- →次回 [twitter:@jsakamoto] さん 「F# で正規表現デザイナ ASP.NET MVC アプリを作成する」
はじめに
今年の 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
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:自分こそが初心者だとも