SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

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

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

非同期ワークフロー

 次にもうひとつのビルトインのコンピュテーションエクスプレッションである非同期ワークフローについて説明します。

 F#では、非同期ワークフローというコンピュテーションエクスプレッションを用いる非同期な計算をサポートしています。非同期というのは、他の作業の実行を妨げないということです。計算が、現在のスレッドで実行が継続されるように、非同期で(バックグラウンドスレッドで)実行されます。

非同期ワークフロー
非同期ワークフロー

 非同期ワークフローの構文は以下のようになります。エクスプレッション部分には、同期あるいは非同期の計算がF#コードで記述されます。

[構文]“async”を用いた非同期コンピュテーション
async { エクスプレッション }

 非同期処理を実行する方法はいくつかありますが、一般的なのは、Asyncクラスオブジェクト(非同期ワークフロー)を作成し、トリガー関数のような関数を利用して実行する方法です。

 以下のリスト5は、リストurlsの各要素のWEBサイトのstringの長さを非同期ワークフローを用いて計算して表示させるものです。

[リスト5]let !による非同期操作
open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urls = ["http://www.yahoo.co.jp/"; " http://codezine.jp/"]
let testWebLength(url: string) =
    async {
        let uri = new System.Uri(url)
        let wc = new System.Net.WebClient()
        let! testsite = wc.AsyncDownloadString(uri) //※1 非同期処理
        printfn "%d characters" testsite.Length
    }

//※2 以下トリガーとなる関数

let testRun () =
    urls
    |> Seq.map testWebLength
    |> Async.Parallel  //※3 複数処理をまとめて実行
    |> Async.RunSynchronously  //※4 非同期ワークフロー実行
    |> ignore //戻り値がunitなので

testRun();;

 ▼

26149 characters
92954 characters

val urls : string list = ["http://www.yahoo.co.jp/"; " http://codezine.jp/"]
val testWebLength : string -> Async<unit>
val testRun : unit -> unit

 testWebLengthは、与えられた文字列(url)が示すWEBサイトのページの文字列を非同期にダウンロードし、その長さを表示します。

 非同期ワークフローのエクスプレッションには、同期される式もあります。メソッドを非同期に呼び出す場合は、letバインディングではなく、let!を使用します(※1)。let!を使用すると、計算の実行時に、他の計算またはスレッドに対して計算を続行できます。let!バインディングの右側の値(Async<’T>型)が戻されると、他の非同期ワークフローの計算が再開します。

 ※2において非同期ワークフローは作成され実行されるのを待つ状態にあります。

 その後、Asyncクラスのトリガー関数となるもので非同期ワークフローを開始させる必要があります。このとき、リスト5では、URLのリストを引数として渡し各要素に対する、非同期ワークフロー計算を平行して実行できるようにAsync.Parallel関数(※3)でまとめ、Async.RunSynchronously関数(※4)で戻り値のある非同期処理を実行するtestRunメソッドを作成してます。そして、最後にこのtestRunメソッドを実行して非同期ワークフローが実行されます。

 次により汎用的なコンピュテーションエクスプレッションの概念について説明します。

コンピュテーションエクスプレッション

 コンピュテーションエクスプレッションは構造体を独自のルールにて構文解析し生成・操作し、シーケンスエクスプレッションや非同期ワークフローはこの生成・操作する構造体をシーケンスや非同期処理に特化したものなのです。

 コンピュテーションエクスプレッションは、独自の構文解析ルールに従って計算したいコード(エクスプレッション)部分(図※1)と、その独自ルールを定義するライブラリコンポーネントから成り立ちます。このライブラリコンポーネントをビルダー(図※2)と呼び、そのためのクラスをビルダークラスと言います。

コンピュテーションエクスプレッションとビルダー
コンピュテーションエクスプレッションとビルダー

 以下はこの汎用的なコンピュテーションエクスプレッションの例です。

  • バインド時に“Binding xxx(値)”と処理内容を表示する
  • returnが、”Returning xxx(値)“と処理内容を表示する

 以上のようなルールで構文解析するコンピュテーションエクスプレッションを作成するとします。

 まず、構文解析ルールとなる、ビルダークラスをモジュール、testModuleとして定義します。

[リスト6]testModule.fs
module testModule

//※1 ビルダーのメソッドを実装する関数
let dobind x testfunc = 
    printfn "Binding %A." x 
    testfunc x
let doreturn x =
    printfn "Returning %A" x
    fun () -> x  //エクスプレッションが値を戻す事を可能にする。

//※2 コンピュテーションエクスプレッション用ビルダークラス
type TestBuilder() =
    member this.Bind(x, testfunc) = 
        dobind x testfunc  
    member this.Return(x)  = doreturn x
//※3 グローバルインスタンス作成
let testce = new TestBuilder()

 ビルダークラス内に直接ルールとなる関数(例えばprintfn "Binding %A." x)を記述することが可能ですが、上記の例では、それらの関数をメソッドを実装する関数としてをあらかじめ定義しています(※1)。

 ビルダークラス定義の構文は以下のようになります。

[構文]ビルダークラス
type クラス名 =
    member 識別子.メソッド(引数) =
        ビルダーオブジェクトとして定義される関数
    :
    :

 メソッドの部分にはコンピュテーションエクスプレッションの計算(エクスプレッション部分)内で使用できるメソッドが入りメンバーとして定義されます。ビルダークラスにて定義できるメソッドの種類とその呼び出し方の詳細はマイクロソフトなどの資料で確認してください。

 このように定義されたビルダークラスは通常、グローバルインスタンスをひとつ持ちます(※3)。以上の例では、testceがそれになります。これがコンピュテーションエクスプレッションの構文においてビルダーオブジェクトネームになります。

 次に上記のルールで構文解析させるエクスプレッション(計算)の部分です。

[リスト7]Program.fs
open testModule

//testceオブジェクトを生成
let testCE = testce {
   let a = 1     
   let! b = 2    //※4 エクスプレッション内のlet!はビルダークラスのBindメソッドを呼ぶ
   let! c = 3    //※5
   let testSum = a + b + c   
   return testSum  //※6 エクスプレッション内のreturnはビルダークラスのReturnメソッドを呼ぶ
   }

// コードを実行
let testresult = testCE()

 ▼

Binding 2.
Binding 3.
Returning 6

 コンピュテーションエクスプレッションは以下のような基本構文で表すことができます。エクスプレッションには、独自の構文解析で計算したい式を記述します。

[構文]コンピュテーションエクスプレッション
ビルダーオブジェクトネーム {エクスプレッション}

 リスト7のエクスプレッション部分{}に記述されている計算は、変数に3つの値をバインドさせ、うち二つはバインドを独自の解析ルールを使用して行い、3つの値の合計を返すという計算です。※4※5※6の部分では、ビルダークラスに定義されたメソッドを呼んでいます。

 let!は通常のletバインドではなく、ビルダークラスのBindメソッドを呼びます。したがって、リスト7では、※4の部分は”Binding 2”と表示しながら、バインドを行います。同様に、return(※6)はビルダークラスのReturnを呼びます。Returnはdoreturnを呼び、Returning xxx(testSumの値)を表示します。

まとめ

 コンピュテーションエクスプレッションはF#のもっとも優れた機能の一つでもあります。単独の機能として考えるのではなく、シーケンスエクスプレッションと非同期ワークフローを組み合わせるなど、用に応じて適切な種類のコンピュテーションエクスプレッションを使いこなしてください。

 以上でC#プログラマのためのF#入門は終わります。本連載では基本機能を解説しましたが、既存プログラムと併用しながらF#を応用することで、より効率的なプログラム作成を目指してください。ありがとうございました。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
C#プログラマのためのF#入門連載記事一覧

もっと読む

この記事の著者

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

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

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

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/5510 2010/10/29 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー