Functional Programming in C# まとめ 8章
7章のまとめは、こちら
8. Working effectively with multi-argument functions
この章では以下の内容を説明する
- コンテナ型と多引数関数を利用する
- monadicな型とLINQシンタックスを利用する
- プロパティベーステストの基礎
8.1. Funcction application in the elevated world
コンテナ型用のApply関数を考える。以下のように定義する。
// Aはfunctor
Apply: A(T -> R) -> A<T>-> A<R> // 1引数関数への適用
Apply: A(T1 -> T2 -> R) -> A<T1> -> A(T2 -> R) // 2引数関数への適用
Apply: A(T1 -> T2 -> T3 -> R) -> A<T1> -> A(T2 -> T3 -> R) // 3引数関数への適用
Mapが引数に取る関数は、1引数関数だが、何引数関数であっても、その関数をカリー化することで、1引数関数(ただし、戻り値が、引数が一つ減った関数になる)にすることができるので、何引数関数であっても、Mapを適用することができる。
しかし、たとえば、2引数関数でMapを適用すると以下のような結果が得られる
A<FUNC<T2 -> R>>
このようにコンテナに関数が入った状態になる。これに対して、Applyを使うことで引数を適用することができる。
また、関数をコンテナの中に入れるには、単にReturnを使って実行してもよい。たとえば、OptionであればSomeを使う。だたし、Applyが適切に実装されていれば、どちらの順序で関数を実行しても結果は同じになる。たとえば、Optionでは以下の実行結果はいずれも等しくなる。
Some(3).Map(multiply).Apply(Some(4));
// => Some(12)
Some(multiply)
.Apply(Some(3))
.Apply(Some(4));
// => Some(12)
これをApplicative則という。※実際にはApplicative則は4つあるとのこと。
8.1.3. An introduction to property-based testing
ここで、プロパティベーステストの紹介。これは、関数がある法則や性質を満たすことがわかっている場合に、その法則や性質を満たしているのかをテストする、というテストの方法。ここでは、上のApplicative則を満たしているか?というテストを書く方法を通じて使い方を解説している。
LOBアプリのテストでも実際に役に立つ。以下の記事を参照。
Choosing properties for property-based testing
8.2. Functors, applicatives, monads
functors、applicatives、monadsの比較
パターン | 必要な関数 | シグネチャ |
---|---|---|
Functor | Map | F<T> -> (T -> R) -> F<R> |
Applicative | Return<p> Apply |
T -> A<T> <p> A<T -> R> -> A<T> -> A<R>
|
Monad | Return<p> Bind |
T -> M<T> <p> M<T> -> (T -> M<R>) -> M<R>
|
MapはApplyを使って定義でき、ApplyはBindを使って定義することができる。つまり、
- MonadはApplicative
- ApplicativeはFunctor
ただし、Applyは実装にBindを使わず、独自実装にした方が便利な場合がある。
8.3. The monad laws
ReturnとBindは以下の3つの性質に従う必要がある:
- 右単位元
- 左単位元
- 結合律
8.3.1. Right identity
m == m.Bind(Return)
8.3.2. Left identity
Return(t).Bind(f) == f(t)
8.3.3. Assciativity
結合律が何を意味するのか加算を使って説明する。3つ以上の数を加算する場合、どうグループ分けするかは関係ない。
(a + b) + c = a + (b + c)
Bindもまた2項演算子と考えられ、>>=
で表すことができる。つまり、m.Bind(f)
は、m >>= f
と表せる。
Bindもある意味では結合律を満たすことがわかっている。
(m >>= f) >>= g == m >>= (f >>= g)
左側は、m.Bind(f).Bind(g)
と同じであり、通常のBindの使い方。
右側はシンタックスとして意味をなさない。なぜなら、>>=
演算子の左側は、monadicな値であるべきだから。しかし、fはラムダ式を使って、x => f(x)
と展開できる。
m >>= (x => f(x) >>= g)
m.Bind(f).Bind(g) = m.Bind(x => f(x).Bind(g))
さらにgをラムダで展開して
m.Bind(x => f(x).Bind(y => g(y)))
上の展開から、例えば、Option型の値を2つ取る関数を以下のように実行することができる。
static Option<int> MultiplicationWithBind(string strX, strinng strY)
=> Int.Parse(strX).Bind(x => Int.Parse(strY).Bind<int, int>(y => multiply(x, y)));
しかし、これは正常に機能はするが、書きたくはない。3引数以上の例はもっと複雑になる。
8.4. Improving readablity by using LINQ with any monad
LINQのSQLクエリっぽく書く方の構文を使って、F#のコンピューテーション式で実現するものやHaskellのdoブロックのような機能を実現しよう、という節になります。
これを実現すると、上のコードが以下のように書けるようになります。
from x in Int.Parse(strX)
from y in Int.Parse(strY)
select multiply(x, y)
8.5. When to use Bind vs. Apply
ここでは、上の例のようなエラーとなる可能性のある引数を複数取るような関数を適用する場合に、Applyで適用した場合と、Monadicに適用した場合で「差がある」、という議論になります。
その差とは、
- Applyで適用した場合には、エラーがすべて拾える
- Monadで適用した場合、エラーは最初の1つのみ拾える
としています。が、私には、単に著者のライブラリの実装の「差」の問題のようにしか思えませんでした。著者も、Monadでも複数のエラーを収集するような実装にすることもできる、としたうえで、その実装は一般的でない、としています。つまり、「ApplyとBindの違い」のような節のタイトルとその後の展開なのですが、実際には著者のライブラリの実装の違い、ということになります。
もちろん、こういった違いをつけることが実践的である、という著者の判断だと思います。が、ApplyやBindが本来備えているべき性質についての話ではないので、注意が必要です。
Discussion
LINQ 使って do 構文を再現できるのは面白いですね!
とありますが、基本的に複数エラーを収集できるものはモナドにならないと思います (Haskell でも Validate は Applicative 止まり)
というのも、
bind :: Validate e a -> (a -> Validate e b) -> Validate e b
を作るときに、第一引数が Right でないと a 型の値が入手できず、第二引数の実行が不可能だからです
Haskell の Monad では Applicative との違いをつけるのは許されておらず、apply は bind から導出できるものと同等でなければなりません
他の言語での Monad もおおむねそうで、ここを変えるというのはかなり著者独自の見解が入ってそうですね
なのでライブラリの実装の差であるというのは正しいと思います
コメントありがとうございます。
関数型プログラミングの本を2冊程度読んだ知識なので、こういったコメントを頂けると勉強になります。
本書はところどころ、著者の強めの主張が入っているようなのですが、どこまでが一般的な考え方でどこからが著者の主張なのかがわかりにくいので、少し読みづらいです。※単に、私の英語力の問題かもしれませんが…。