ログ日記

作業ログと日記とメモ

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)] }

今のところこんな感じで作っている。