コンピュテーション式のSourceメソッドを試す

前提

以下のエントリを読んでいることが推奨されます。

詳説コンピュテーション式

注意

src(e) は、ビルダーがSourceメソッドを持っており、 かつ最も内側のForEachがユーザによるもの(変換により生成されたコードではなく、ユーザが書いたコードであるということ)である場合のみ、 b.Source(e) に変換されます。

2番目の条件は仕様書からのものですが、どうも実際の実装はちょっと違っているようです。コードを軽く眺めただけですが、ForEachとForEachThenJoinOrGroupJoinOrZipClauseのForEach部分とLetOrUseBangの場合に、ユーザコードかどうかの判定をしているようで、そのほかの部分ではビルダーにSourceメソッドがあればそれを呼び出しているようです。 そのため、return! eはビルダーにSourceメソッドが存在すればb.ReturnFrom(b.Source(e))に、存在しなければb.ReturnFrom(e)に変換されます。

詳説コンピュテーション式

このことから、将来のF# のバージョンによっては、仕様書通りの挙動になって今回のサンプルコードが動かなくなる可能性が十分に有り得ます。

Source メソッドで何ができそうかを考える

src が出現する変換規則は以下の通りです。

T(let! p = e in ce, V, C, q) =  T(ce, V Å var(p), lv.C(b.Bind(src(e),fun p -> v), q)
T(yield! e, V, C, q) = C(b.YieldFrom(src(e)))
T(return! e, V, C, q) = C(b.ReturnFrom(src(e)))
T(use! p = e in ce, V, C, q) = C(b.Bind(src(e), fun p -> b.Using(p, fun p -> {| ce |}0))
T(for p1 in e1 do joinOp p2 in e2 onWord (e3 eop e4) ce, V, C, q) =
    Assert(q); T(for pat(V) in b.Join(src(e1), src(e2), lp1.e3, lp2.e4, 
    lp1. lp2.(p1,p2)) do ce, V , C, q)
T(for p1 in e1 do groupJoinOp p2 in e2 onWord (e3 eop e4) into p3 ce, V, C, q) = 
    Assert(q); T(for pat(V) in b.GroupJoin(src(e1), 
    src(e2), lp1.e3, lp2.e4, lp1. lp3.(p1,p3)) do ce, V , C, q)
T(for x in e do ce, V, C, q) = T(ce, V Å {x}, lv.C(b.For(src(e), fun x -> v)), q)
T(do! e;, V, C, q) = T(let! () = src(e) in b.Return(), V, C, q)

ぱっと思いつくのは、

  • 互換のある型が渡された場合に変換を行う
  • (Stateを用いた実装にしている場合)特定の型のみ、後続の計算を継続する/破棄する

です。後者は特にやる理由がないので、ここでは前者について見て行きましょう。

例:Souce メソッドを利用して型を変更する

コンピュテーション式に Source メソッドを実装して、互換のある型から値を取り出してみましょう。

例として、Basis.Core の OptionBuilder に Result 型を渡せるようにします。

open Basis.Core

module Hoge =

  type Option.OptionBuilder with
    member this.Source(x) = Result.toOption x
    member this.Source(x: _ option) = x
    
  let res = option { return! Success 10 } // Some 10

この例の場合、 Result 型の場合は Option に変換され、それ以外の型はそのまま ReturnFrom 渡されます。Result 用の関数は定義したけど Opation のコンピュテーション式内でも同じものが使いたくなった、という場合に利用できそうです。

まとめ

  • 溢れ出る QueryBuilder 用メソッド臭…
  • 拡張メソッドとオーバーロードのご利用は計画的に。無理のない型ライフを。
  • 型変換が多量に発生することが予測できる時に利用することをおすすめします