そう、F# でやりたかったこと、コンピューテーション式。コンピューテーション式はとりあえず初めての人には難しいとしか思わない。一歩ずつ、一歩ずつやっていこうではないか。
概要
- コンピューテーション式とは
- 理論
- 実装 (Bind, Return)
- 実践
- まとめ
1. コンピューテーション式とは
コンピューテーション式とは:
Computation expressions in F# provide a convenient syntax for writing computations that can be sequenced and combined using control flow constructs and bindings. They can be used to provide a convenient syntax for monads, a functional programming feature that can be used to manage data, control, and side effects in functional programs.
(MSDN より Computation Expressions (F#))
日本語に直してみます? 私がテキトーに。
F# におけるコンピューテーション式は計算を書くための便利な syntax を提供します。それはあなたに制御フローの構築および結合を連続に統合された環境をもたらします。それらはモナドのための便利な syntax を提供するのに使われます。モナドとはいわゆる関数型プログラミングにおける特徴で、データを扱うことや、制御することに使われ、関数型言語に影響をもたらしました。
だいたいこーんな感じ。ちょっと意訳気味。まあもとの文章ぶった切ったりしてる。わかってやってる。元の文章なら for monads になってるので、モナドのための、って訳したけども、モナドのために与えられた便利な機能、って訳すべきなのかも。ちょっとここから意図をくみ取るのは難しいな。まああえてここで monads を挙げているので意図としてはこっちのほうが近いかもしれん。
ただ、検索かけてもらえばわかるんですが、もはやコンピューテーション式はモナドのためだけのものではないといってもいいんじゃないのでしょうか。私も詳しいことが分かりません。一緒にやっていきましょう。
とりあえず上の文章を要約すると、「コンピューテーション式とは連続した制御フローの構築およびそれらの結合をもたらす」ということが言えます。連続した制御フローを構築し、そしてそれらを結合。考え的には push model といえばいいのではないのでしょうか。
ただ、理解してない中でこんなことを言っていて間違っていては恥ずかしいので、この辺で掘り下げるのをやめて実践に入りましょう(笑)*1
2. 理論
上でもあげた通り、コンピューテーション式は制御フローの構築役割を持ちます。これを Builder といいます。object を construction する子、それが builder。
以前*2、シーケンス式を取り扱いましたが、これは標準定義のコンピューテーション式です。つまり、もう使ったようにコンピューテーション式の構文は、
builder-name {| cexpr |}c
です。
いわゆる builder-name が Builder の名称。シーケンス式で言えば seq。
ここで重要なのは builder-name で指定した Builder Object を用いてこれが処理されるということ。
理論的なお話は簡単ですね。ただ、コンピューテーション式を使ってできることが多いので、たぶん初心者は躓くんだよな… 俺も見ていやだったもん。そういうときは、下を見ないで上から順番にやっていけばいいと思うし、そういうのを徐々に紹介する回が複数回にわたって続くんだと思う。ということで、次からはじまるよ!
3. 実装 (Bind, Return)
3.1. Bind
まずは定義。
M<'T> * ('T -> M<'U>) -> M<'U>
なんのこっちゃわからん(´・ω・`)
まだ説明していないのでこうなっても無理もないです。ここで型の名前について軽く説明します。
3.1.1. 型の名前
- A -> B: 入力 A を処理し出力 B を出す関数
- M<'T>: 型引数 T におけるジェネリック型 M
- A * B: 型 A、B をもつタプル型
それでは戻ろう。
まず A * B -> C が見えるので、タプル型を入力して何かが出てくるということである。A についてみると M<'T> という変数を受け付ける。次に 'T -> M<'U> という関数を受け付ける。この 2 つの入力に対して、一つの結果 M<'U> を出力するということが分かる。
ここからわかることは、1 つの変数と1 つの関数が入力されるということだ。初心者の型向けに少し言語を言い換えてみよう。
M<U> Bind( tuple<M<T>, function<M<U>( T )>> xf );
M<U> Bind( Tuple<M<T>, Func<T, M<U>>> xf );
見慣れた形になったからわかったかな。無意味なので、今後は書かないけど初回なので書いてみた。これで F# における型の読み方がわかればいいと思う。
※ 実際に C# や C++ でこのように書く必要性は薄い。そもそも F# より Tuple が気軽に生成できないので、個別の引数として保持すべきだろう。
Bind 関数はただ単にこれだけのことしかしない。Bind がどう呼び出されるかは後ほど。
3.2. Return
最終的に return するときに用いられる。return といえば 1 変数の返答。それでは定義から
'T -> M<'T>
包むだけ。
4. 実践
まずは何といっても触ってみないといけない。普通の F# コードを書いてみます。
let func x = let a = x * 15 let b = a + 13 b
これ、もうわかりますよね。 である。これをコンピューテーション式に書き換えましょうというお話。
とりあえずコンピューテーション式を用いた形式にしてみる。
let func x = hoge { let! a = x * 15 let! b = a + 13 return b }
コンピューテーション式宣言 hoge {} と let のあとの !、b の前に return だ。
まず let! や do! は Bind 関数を呼び出す。そして return は Return 関数を呼び出す。つまりこう展開できる。
let func2 x = hoge.Bind (x * 15, fun y -> hoge.Bind (y + 13, fun z -> hoge.Return z))
展開した結果を見てもらえばわかるが、展開した式はとても見づらい。これが先ほどのようにかけるのだ。シンプル。
最後に今回使った何も変哲のない Builder を書く。これもとてもシンプル。
type HogeBuilder() = member this.Bind (x, f) = f x member this.Return x = x let hoge = new HogeBuilder()
ただ、ここまでやっても使い方が見えてこないって感じ。
ここまでで言えることは関数の間に何かの動作を入れ処理を行うということ。それだけ。
5. まとめ
ここまで書いてみたんだけど、書いた本人の頭の上に ? がついてる感じ。ずっとついてる。どういう方向で使えばうまくいくのか想像できないってところが本音。本当に次もコンピューテーション式を書き続けて、自分なりの答えが叩き出せるか疑問といったところではある。
午前中のやったこと、これだけ。コンピューテーション式、理解は簡単だけど、有用に扱うのは難しそう。所詮は慣れなんだろうが。
おしまい。
*1:とりあえずこの記事を書いてみただけで、まだ実際にコンピューテーション式を書いてはいないのです…