Webページごとのアクション関数
PHPなどのアクションクラスやアクションメソッドの場合、アクションを実行する関数はRequestsオブジェクトを受け取ってResultsオブジェクトを返すように作っていた。
Haskellの型で書くと
... data Results = [Result] type Act = Requests -> Results
のようになる。ここのActはHaskellのIOアクションではなくてWebフレームワークで使われているWeb画面を表示するアクション関数のこと。
これで複数のアクションを実行するには
runActs :: Requests -> [Act] -> Results runActs req as = concatMap ($ req) as
のような関数を用意して、Actのリストを実行する。
アクションの連結は
-- 例えば、データ登録(a1)した後にデータ表示(a2)するアクションをまとめる actAct :: Act -> Act -> Act actAct a1 a2 r = (a1 r) ++ (a2 r)
こうなるのかな。
これで途中まで作っていたけれど、関数型言語だとストリームとかフィルターとかを意識しながら型を考えた方がいいかと思って引数と結果の型を合わせてみる。
data Context = Context { requests :: Requests , results :: Results } deriving Show type Act = Context -> Context
こうすると、
actAct :: Act -> Act -> Act actAct a1 a2 = a1 . a2
連結がシンプルになる。
ここで、大抵のActはデータベースやファイルなどのIOを扱うことを考えて
type Act = Context -> IO Context
という型にActを変更する。Requestsに対してパターンマッチをしたいので、受け取る引数はIOを外しておく。
連結は
import Control.Monad ((>=>)) ... actAct :: Act -> Act -> Act actAct a1 a2 = a1 >=> a2
(>=>)演算子を使う。
フレームワークにはアクション名に対して起動するAct関数を登録する。
type EntryPoint = (String, Act) type EntryPoints = [(String, Act)] entryPoints :: EntryPoints entryPoints = [("Top", top) ,("View", view) ,("Register", register >=> view) ] top :: Act top = return . id view :: Act view c@(Context { requests = req }) = do conn <- connectDb res <- findData conn return $ c { results = results c ++ dbToResults res } register :: Act register c@(Context { requests = req }) = do conn <- connectDb res <- saveData conn req let msg = if res then "success" else "error" return $ c { results = [Result ("message", msg)] }
今のところこんな感じで作っている。