この記事は [http://connpass.com/event/9758/:title] の初日の記事です。
12月になってしまいましたね、進捗どうですか?私は駄目です。
タイトルは"コンピュテーション式の Tips"となっていますが、中身は Basis.Core や <a href"https://github.com/persimmon-projects/Persimmon>"Persimmon 内で使われているコンピュテーション式の実装方法を勝手に紹介するというだけです。
この記事は 2014/12/01 JST 現在書きかけです。 よって、後々更新したときのために gist で diff を残しておきます。
[https://gist.github.com/pocketberserker/8f2bda2dddd014b3e20e:title]
- 目的: Haskellのdo構文をエミュレート
- 実装に使うもの: monad laws, Return, Bind, ReturnFrom (, Zero)
コンピュテーション式で一番よく知られている(そして誤解するかもしれない)パターン。
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: [http://www.slideshare.net/bleistift/yield-and-return:title]
- 目的: 最小限の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
- 目的: キーワードの呼び出し順序を固定したい
- 実装に使うもの: 判別共用体(DU), custom operator, Sourceメソッド
- 目的: unitとそれ以外の型で挙動を変えたい
- 実装に使うもの: 判別共用体, Sourceメソッド
- 目的: コンピュテーション式内で使われたメソッドを解析して操作を制御したい
- 実装に使うもの: Quoteメソッド, Runメソッド
query ビルダーが公式サンプルである。
TODO: write
カスタムオペレーターに関するおまけ。
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"!