ワークフローとリスト処理
ワークフローの構造、特にBindの中身をじっと見ていると、どこかリスト処理を髣髴とさせる構造が見えてきます。今回、その辺の空気がどのあたりから感じられるのかを探ってみたいと思います。
1.Bindの中身
すでに何度も貼ったコードですが、もう一度よく見てみましょう。
member this.Bind(
result : StatefulFunc<'state, 'a>,
restOfComputation : 'a -> StatefulFunc<'state, 'b>
) =
StatefulFunc(fun initialState ->
let result, updatedState = Run result initialState
Run (restOfComputation result) updatedState
)
引数をよくよく見ると、resultは先頭の命令の処理(の結果)で、restOfComputationは残りの処理になっています。この構造は、さんざん見慣れたリスト処理と非常によく似ています。
処理 |
最初の引数 |
二番目の引数 |
ワークフローのBind |
先頭の関数 |
残りの関数を結合したもの |
List処理 |
先頭の要素 |
残りの要素のリスト |
ということは、ループや分岐を含まないワークフローなら、リスト処理と同じ流儀で書けそうな気がします。さっそくやってみました。
ワークフローの定義
StatefulFuncのAdd、Multiplyをそのまま使って、ワークフローっぽいことをしてみることにします。
まずは、
let calculatorActions =
state {
do! Add 1
do! Add 2
do! Add 3
do! Add 4
do! Multiply 3
}
というようなワークフローを、以下のようにリストとして書くことにします。
let jobs = [Add 1; Add 2; Add 3; Add 4; Multiply 3]
これを処理する関数を書けばいいわけです。
Bindっぽい関数の定義
let rec doJobs (l : StatefulFunc<'state, 'a> list) state =
match l with
| [] -> state
| h :: t -> let result, nextState = Run h state
doJobs t nextState
ジョブのリストと、初期状態を与えると、最初の一つを取り出して実行し、その結果を残りのジョブに与えるという、リスト処理としては非常にありがちなプログラムです。foldでも書けると思います。
おしまいの2行あたりに、Bindの面影が見て取れますね。
実行してみましょう。
実行結果
ジョブのリストを処理するコードは以下のようなものです。
let res, lastState = doJobs jobs (0, ([] : string list))
そして実行結果。
val res : int = 30
val lastState : string list = ["3を乗算"; "4を加算"; "3を加算"; "2を加算"; "1を加算"]
ちゃんとそれっぽい動きをしていますね。
まとめ
StateBuilderがBindの最初の引数として受け取るのが、処理結果というよりも「処理内容を書いた指示書」のようなものになっているため、それを単純にリスト化するだけで類似コードを書くことができたのですが、一般的なワークフローは処理そのものが先に行われ、Bindの最初の引数にはその結果だけが渡されるのが普通です。こういう場合には無論リスト化は難しいわけで、「沢山の命令を並べ、前の処理の結果を後ろに渡しながら、処理と処理の間に必要な処理を挟みつつ最後まで処理をした結果を返す」というようなことをしたい場合は素直にワークフローを使ったほうがよさそうです。
ただ、ワークフローという発想が「連続した命令の処理」⇒「ラムダ式のリストを受け取り順に処理する関数」⇒「どこかで処理をした結果を受け取り、継続にその結果を渡すワークフロー」というような流れで出てきたのではないだろうか・・・というような想像をすることはできそうです。もちろん、ワークフロー(モナド)は圏論(とやら)をベースに大変な理論的基盤を持ったもののようですが、発想の発端は案外このあたりなのかもしれません。
(文責:片山 功士 2011/12/30)
今日: - 人
昨日: - 人
トータル: - 人
最終更新:2012年02月03日 01:58