コンピュテーション式
コンピュテーション式は、個人的にはF#の中で最も理解が難しい文法だと思っています。
ですので、初めての方はここは飛ばしても構いません。
Microsoftのドキュメントによると
F# のコンピュテーション式には、制御フローのコンストラクトとバインドを使用してシーケンス化および結合できる計算を記述するための便利な構文が用意されています。
とありますが何のことやらさっぱりわかりませんよね。
まず、コンピュテーション式の構文を見てみます。
- コンピュテーション式の構文
-
builder-expr { cexper }
構文自体はシンプルです。
組み込みの構文にはasync,query,task,seqなどがあり
実際に使う例をあげると次のようになります
コンピュテーション式のサンプル(task)
open System.Net.Http;
let downloadTextAsync (url: string) =
task {
use client = new HttpClient()
let! text =
client.GetStringAsync url
return text
}
|> Async.AwaitTask
downloadTextAsync "http://fsharpintro.net" |> Async.RunSynchronously |> printfn "%s"
これはF#6から導入された組み込みのtaskを用いたサンプルです。
プログラムを実行するとF#入門のトップページのHTMLソースが表示されます。
コード自体について解説はしませんが、重要なことはtask{}で式を囲っていることと
let!やreturnなどの構文が使えるようになっている点です。
組み込み以外のコンピュテーション式を利用するには、ビルダークラスと呼ばれるものを
準備する必要があります。
ビルダークラスに特定のメソッドを定義するとそれに応じて
let! do! yield returnなどの構文がcexprの個所で使えるようになります。
例えばlet!はBind,returnはReturnというメソッドをビルダークラスに用意することで
使えるようになります。
let!とreturnの二つは特に重要で、様々なモナドの利用をする際に定義が必須になります。
最初に簡単なサンプルを挙げてみます。
コンピュテーション式のサンプル(async)
type Container<'a> = Container of 'a
type MyBuilder() =
member __.Bind (x, f) =
match x with
| Container (y) -> f y
member __.Return (x) = Container(x)
let mycon = MyBuilder()
mycon {
let! x = Container 10
let! y = Container 15
return x + y
}
|> printfn "%A"
この例では、BindとReturnを定義してlet!とreturnを使用しています。
まずは簡単なReturnから。
Returnの一般的な型は'T -> M<'T>です。
上のコードでは'a -> Container<'a>ですね。Containerで包むだけです。
次にBindの一般的な型はM<'T> * ('T -> M<'U>) -> M<'U>です。
これを初めて見た人は面食らうと思いますが説明していきます。
まずここでのMはContainerです
bindの型はContainer<'a> * ('a -> Container<'b>) -> Container<'b>になります
Container<'a>と('a -> Container<'b>)は直接作用させることができません。
まず、Container<'a>の中の'aを取り出して('a -> Container<'b>)に作用させる必要があります。
Container (y) -> f y
この部分(Container (y))でContainre<'a>の中身yを取り出してf('a -> Container<'b>)を作用させています。その結果新しいContainer<'b>が残ります。
上のmycon {}の個所は以下のコードと等価です(これもそのまま実行できます)
mycon.Bind(Container 10,fun x ->
mycon.Bind(Container 15,fun y ->
mycon.Return(x+y)))
|> printfn "%A"
let! xとした個所もfun xとなっている個所もどちらのxもContainer 10の
コンテナを外した10が入ります
let!するとM<'T>のMがとれて'Tだけになると覚えておくと良いと思います。
最後にreturnでまたContainerに包まれて計算が終了します。