TL;DR

内容

第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 つを入力に取るので、一致しない。

キーワード