コンピュテーション式の Tips
この記事は F# Advent Calendar 2014 - connpass の初日の記事です。
12月になってしまいましたね、進捗どうですか?私は駄目です。
タイトルは"コンピュテーション式の Tips"となっていますが、中身は Basis.Core や "Persimmon 内で使われているコンピュテーション式の実装方法を勝手に紹介するというだけです。
はじめに
この記事は 2014/12/01 JST 現在書きかけです。 よって、後々更新したときのために gist で diff を残しておきます。
モナド
コンピュテーション式で一番よく知られている(そして誤解するかもしれない)パターン。
do 構文のエミュレーションに必要な最低限のメソッドのみ実装する。
// example type OptionBuilder() = member __.Bind(x, f) = Option.bind f x member __.Return(x) = Some x member __.ReturnFrom(x: _ option) = x let option = OptionBuilder() let l = [10..99] option { let! a = List.tryFind (fun x -> x % 2 = 0) l let! b = List.tryFind (fun x -> x % a = 9) l return (a, b) }
フロー制御
- 目的: 使うキーワードによって後続の計算を分岐させたい
- 実装に使うもの: State, CPS, (try-with)
これは既に id:bleis-tift さんが詳しく解説しているので、紹介にとどめておきます。
more information: yield and return (poor English ver)
例外
- 目的: 最小限のBuilderで例外をハンドリングする
- 実装に使うもの: try-with, custom operator
拡張メソッド
- 目的: 既存のコンピュテーション式を拡張したい
- 実装に使うもの: 拡張メソッド
//examle 1 open Basis.Core 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
//example 2 open System.Threading.Tasks type AsyncBuilder with member __.Source(x: Task<_>) = Async.AwaitTask(x) member __.Source(xs) = Async.Parallel(xs) let task = async { return 1 } |> Async.StartAsTask async { let! x = task return printfn "%d" (x + 1) } |> Async.Start async { let! xs = [0..10] |> List.map (fun x -> async { return x }) return printfn "%A" xs } |> Async.Start
型によるDSL制御
- 目的: キーワードの呼び出し順序を固定したい
- 実装に使うもの: 判別共用体(DU), custom operator, Sourceメソッド
unit
- 目的: unitとそれ以外の型で挙動を変えたい
- 実装に使うもの: 判別共用体, Sourceメソッド
Quotation Evaluation
query ビルダーが公式サンプルである。
カスタムオペレーター
TODO: write
why reverse?
カスタムオペレーターに関するおまけ。
type SampleBuilder() = member __.Yield(()) = Seq.empty member __.Yield(x) = x member __.For (state, _) = state [<CustomOperation("case", AllowIntoPattern=true)>] member inline __.Case(source, case) = seq { yield! source; yield case } [<CustomOperation("run")>] member __.SideEffect(source: _ seq, [<ProjectionParameter>]f: _ * _ -> unit) = source |> Seq.iter f let sample = SampleBuilder() (* expected output: 1, 2 3, 4 but was: 2, 1 4, 3 *) sample { case (1, 2) case (3, 4) into (x, y) run (printfn "%d, %d" x y) }
なぜかタプルが逆順で出力されるのですよねー…仕様なのかバグなのか。
おわりに
TODO: write
余談
最初は FSharp.Karma や Higher や (未完成の) FreeF の解説を書こうかと思っていたけど、テンションがあがらなかったので断念。 それもこれも FreeF が未完成なのが悪い。 Please give me "higher kinded types"!