Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

F#キーワードを再定義するコンピュテーション式

C#プログラマのためのF#入門(10)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/10/29 14:00

 本連載では、C#で「最低限」なんらかのコードを書いたことのあるプログラマーを対象に、関数型プログラミング言語「F#」について数回にわたり解説していきます。今回は連載の最終回として、コンピュテーションエクスプレッション(計算式)について紹介します。

目次

はじめに

 連載「C#プログラマのためのF#入門」最終回となる今回は、コンピュテーションエクスプレッション(計算式)について説明します。

 コンピュテーションエクスプレッションは、F#で記述されたあるコードをどのように動作させるか独自の解釈で定義したり、他の言語に変換する機能です。F#言語構造のサブセットの意味解析を上書きするような機能と言えます。例えばwhile、return、yieldなどのF#キーワードの仕様を再定義できるのです。

 関数型プログラミングのデータ制御、および副作用の管理に使用できる関数型プログラミングの機能であるモナドのF#版とも言えるでしょう。モナドと呼ばない理由は、響きが悪い(とっつきにくい)とか、副作用がF#の型システムによってトラックされない(モナドでは副作用は全てモナドにリフトされる)ようにできるところなど、小さな違いがあるからだそうです。コンピュテーションエクスプレッションはC#のLINQのquery構文とも似ています。

 コンピュテーションエクスプレッションには、既にプロセスや処理が規定化されていたり自動化されて組み込まれている(ビルトイン)コンピュテーションエクスプレッションと、それとは別にユーザー定義で行う物があります。

 この二つのコンピュテーションエクスプレッションの差が今の段階ではわかりにくいと思いますので、はじめにビルトインコンピュテーションエクスプレッションであるシーケンスエクスプレッションと非同期ワークフローについて説明し、その後に関係を明確にしたいと思います。

シーケンスエクスプレッション

 まず、シーケンスエクスプレッションを理解する上で基本となるシーケンスについて説明します。

 シーケンスとは、1つの型の複数要素からなる、コレクションです。シーケンスの個々の要素は必要に応じて計算(生成)されます(遅延評価)。そのため、すべての要素を使用するとは限らない場合、リストより効率的であることが多いです。

 シーケンスはseq<'T>型で表され、ジェネリックなIEnumerable(T)を実装するすべての型は、シーケンスが期待されるすべての場所で使用できます。

[構文]シーケンス
seq{要素}

 シーケンスを用いて、以下のようなデータをあらわすことができます。これは、1から1億までの整数を表していますが、遅延評価により実際に1億個の要素のデータ構造が作成されるのではなく、必要に応じてその数の要素が作成できるという可能性をもったシーケンスをあらわしています。

[リスト1]シーケンスの例
seq {1 .. 100000000}

 seqであらわされるシーケンスは、シーケンスモジュールとして定義されているファンクションや集約演算子で使用可能です。例としては、Seq.map、Seq.filterなど、リスト用モジュールとして定義されているもの(list.map、 list.filterなど)と似たものも多々あります(詳細はマイクロソフトのページなどでご確認ください)。

 これらのモジュールを使用してシーケンスを以下のように直接作成・操作できます。Seq.initは与えられた第一引数を最大項目数とし、第二引数のファンクションにて要素を作成し、新しいシーケンスを作成するメソッドです。

[リスト2]シーケンス用集約演算子を用いたシーケンスの初期化
let testSeq = Seq.init 3 (fun i -> i * 2);;
testSeq;;

 ▼

val testSeq : seq<int>
> val it : seq<int> = seq [0; 2; 4]

 上記のようなシーケンス用集約演算子を用いてシーケンスを操作する以外に、F#にはシーケンスエクスプレッションという構文を用いてシーケンスの値を作成したり操作する方法があります。その代表的なものが以下のforを用いたものです。与えられたパターンにシーケンスの値をひとつずつ適用させ、->(yieldと呼ぶ。do yieldでも置き換えられる)で、シーケンスの一部となるデータを生成します。

[構文]forを用いたシーケンスエクスプレッション
seq {for パターン in シーケンス -> エクスプレッション}

 つまり、上記の構文はSeq.map(集約演算子)と同じ事を意味します。

 以下の例はiに1から3の値を順にバインドし、i * iを実行して値を生成しコレクション(シーケンス)を返します。

[リスト3]Seq.map(集約演算子)とシーケンスエクスプレッションを用いた例
Seq.map (fun i -> i*i) [1..3];; //集約演算子
seq { for i in 1 .. 3 do yield i * i };;  //シーケンスエクスプレッション

 ▼

val it : seq<int> = seq [1; 4; 9]
>
val it : seq<int> = seq [1; 4; 9]

 上記の基本構文に補助的に、以下のような句を使用することで、よりカスタマイズされたシーケンスを作成できます。

シーケンス用補助句
種類 使用方法 構文
反復 for文を入れ子にできます for パターン in シーケンス do エクスプレッション
フィルター if文による判断 if 式 then エクスプレッション
条件分岐 if文による条件分岐 if 式 then エクスプレッション else エクスプレッション
letバインド letによる通常バインド let パターン = 式 in エクスプレッション
最終生成 値の生成 エクスプレッション、または、yield エクスプレッション
シーケンスの生成 他のシーケンスエクスプレッションからのシーケンスをコレクションに追加 エクスプレッション、または、yield! エクスプレッション

 以下は、これらの補助句を用いたシーケンスエクスプレッションの例です。

[リスト4]シーケンスエクスプレッション
open System.IO

let rec testFiles testdir =  //※1
    seq {   for testfile in Directory.GetFiles(testdir) do  //※2
               yield (testfile)
            for testsubdir in Directory.GetDirectories testdir do  //※3
                yield! (testFiles testsubdir) };;

testFiles @"C:\Users\test1";;

 ▼

val it : seq<string> =
  seq
    ["C:\Users\test1\111.jpg"; "C:\Users\test1\112.jpg";
     "C:\Users\test1\test2\222.jpg"; "C:\Users\test1\test2\test3\333.jpg"]

 yield!は一連の要素をコレクションに追加するのに使用されます。再帰シーケンスエクスプレッションにてよく使用されます。再帰シーケンスエクスプレッションとは、再帰関数同様、シーケンスエクスプレッションを通常の関数のように再帰的に使用するシーケンスエクスプレッションです。

 上記リスト4では再帰シーケンスエクスプレッションtestFilesを利用して、与えられたディレクトリ配下全てのフォルダ内のファイル名をひとつのコレクションに追加しています。

 再帰シーケンスエクスプレッションはrecキーワードを用いて定義します(※1)。エクスプレッション内、まず最初のfor文の部分のエクスプレッションは、引数として与えられたディレクトリにファイルがあればそれをシーケンスとしてコレクションに追加します(※2)。二つ目のfor文部分のエクスプレッションは、さらにフォルダがある場合、そのフォルダを新たな引数としてtestFilesを再帰的に呼び出します(※3)。このとき、yield!によって、戻されるシーケンスがコレクションに追加されます。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XMLD...

  • WINGSプロジェクト 星山 仁美(ホシヤマ ヒトミ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

バックナンバー

連載:C#プログラマのためのF#入門

もっと読む

All contents copyright © 2005-2018 Shoeisha Co., Ltd. All rights reserved. ver.1.5