TL;DR
- ステップを合成するとき、入力と出力の型が合わない問題によく出くわす。
- 依存関係による余計な入力は、部分適用で取り除ける。依存関係の渡し方はDomain Modeling Made Functional 第9章 依存関係の注入を参照。
- 部分適用で解決できないときは、関数アダプタを作るか、命令型の書き方にする。
内容
合成したいワークフロー
Domain Modeling Made Functional 第9章 最初のステップの実装とDomain Modeling Made Functional 第9章 残りのステップの実装で実装したステップを、1 つのワークフローに合成する。目指す形は次のとおりである。
let placeOrder: PlaceOrderWorkflow =
fun unvalidatedOrder ->
unvalidatedOrder
|> validateOrder
|> priceOrder
|> acknowledgeOrder
|> createEventsしかし、このままでは合成できない。validateOrder には依存関係の入力が 2 つ余計にあるので、前後のステップと入力・出力が一致しない。priceOrder も同じである。「形」の異なる関数を合成することは、関数型プログラミングの主要な課題の 1 つである(Domain Modeling Made Functional 第8章 関数合成)。解決策にはモナドを使う方法と、部分適用を使う方法がある。
部分適用で依存関係を取り除く
関数を値として扱えるので(Domain Modeling Made Functional 第8章 ものとしての関数)、依存関係のパラメータだけを先に適用して、入力が 1 つだけの関数を作れる。F# ではシャドーイングができるので、同じ名前で定義し直してよい。Haskell でよく見るように、末尾に ' を付けた名前にしてもよい。
let validateOrder =
validateOrder checkProductCodeExists checkAddressExists
// または
let validateOrder' =
validateOrder checkProductCodeExists checkAddressExistsワークフロー全体は次のようになる。
let placeOrder: PlaceOrderWorkflow =
let validateOrder =
validateOrder checkProductCodeExists checkAddressExists
let priceOrder =
priceOrder getProductPrice
let acknowledgeOrder =
acknowledgeOrder createOrderAcknowledgmentLetter sendOrderAcknowledgment
fun unvalidatedOrder ->
unvalidatedOrder
|> validateOrder
|> priceOrder
|> acknowledgeOrder // ここでエラーになる
|> createEvents部分適用では解決できない問題
まだ問題が残る。acknowledgeOrder の出力はイベントであり、createEvents の入力と一致しない。この場合は、関数アダプタを書くか、命令型の書き方にすればよい。命令型の書き方では、各ステップの出力を明示的に変数に束縛する。
let placeOrder: PlaceOrderWorkflow =
fun unvalidatedOrder ->
let validatedOrder =
unvalidatedOrder
|> validateOrder checkProductCodeExists checkAddressExists
let pricedOrder =
validatedOrder
|> priceOrder getProductPrice
let acknowledgmentOption =
pricedOrder
|> acknowledgeOrder createOrderAcknowledgmentLetter sendOrderAcknowledgment
let events =
createEvents pricedOrder acknowledgmentOption
events