TL;DR
- パイプラインの各ステップに
Resultを導入し、mapErrorでエラー型をそろえ、bindとmapで合成し直す。 - 入力が複数ある関数はこの方法では合成できず、Domain Modeling Made Functional 第10章 コンピュテーション式の導入で解決する。
内容
第9章の各ステップに Result を導入し、合成し直す。ここでは Async と依存関係は無視する。
「注文の検証」と「価格計算」は失敗する可能性があるので、Result を返す。
type ValidateOrder =
UnvalidatedOrder -> Result<ValidatedOrder, ValidationError>
type PriceOrder =
ValidatedOrder -> Result<PricedOrder, PricingError>一方、「注文の確認」と「イベントの作成」は常に成功するので、Result を返さない。
type AcknowledgeOrder =
PricedOrder -> OrderAcknowledgmentSent option
type CreateEvents =
PricedOrder -> OrderAcknowledgmentSent option -> PlaceOrderEvent list合成は 3 段階で行う。まず、ValidationError と PricingError には互換性がないので、両方をケースに持つ選択型を作る。
type PlaceOrderError =
| Validation of ValidationError
| Pricing of PricingError次に、mapError で合成できる形の関数にする。
let validateOrderAdapted input =
input
|> validateOrder
|> Result.mapError PlaceOrderError.Validation
let priceOrderAdapted input =
input
|> priceOrder
|> Result.mapError PlaceOrderError.Pricing最後に bind で合成する。acknowledgeOrder と createEvents は Result を返さないので、map で 2 トラックの関数に変換する(Domain Modeling Made Functional 第10章 Result を生成する関数の連鎖(bind と errorMap、map))。
// val placeOrder : UnvalidatedOrder -> Result<PlaceOrderEvent list, PlaceOrderError>
let placeOrder unvalidatedOrder =
unvalidatedOrder
|> validateOrderAdapted
|> Result.bind priceOrderAdapted
|> Result.map acknowledgeOrder
|> Result.map createEventsただし、このコードはコンパイルエラーになる。acknowledgeOrder の出力は Result<OrderAcknowledgmentSent option, PlaceOrderError> だが、createEvents は PricedOrder と OrderAcknowledgmentSent option の 2 つを入力に取るので、一致しない。