HDBCとReaderTモナドを組み合わせてみる
HDBCでDBを操作するプログラムを書いていると、少なからずDB接続(IConnection)がうっとうしくなってきます。DB操作を行う関数は全て、引数にDB接続を取るからです。
例えばCRUD操作を行う関数を書いたとします。
insertC c = runRaw c "insert into COUNTER values (0)" selectC c = quickQuery c "select * from COUNTER order by COUNT" [] updateC c = runRaw c "update COUNTER set count = count + 1" deleteC c = runRaw c "delete from COUNTER"
これらの関数を使うときは>>=で連結して使うわけですが、DB接続を全部の関数に渡して回る必要があるわけです。
procC :: IO [[SqlValue]] procC = connectPostgreSQL connectionString >>= \c -> return c >> insertC c >> updateC c >> commit c >> selectC c
関数型言語は状態を持たないので、引数でDB接続を受け取るしかない。わかってる。それはわかってるんですがやっぱりうっとーしい。
そこでReaderモナドを使ってDB接続を渡さなくてすむようにしてみようと思ったのですが、かなりはまりました。ReaderモナドとIOモナドの組み合わせではうまく動作しなくて、ReaderTモナドを使わないといけない、と気付くのに時間がかかりました。
ReaderTを使ったCRUD操作は以下のようになりました。
insertT :: IConnection c => ReaderT c IO Integer insertT = ask >>= \c -> liftIO $ run c "insert into COUNTER values (0)" [] selectT :: IConnection c => ReaderT c IO [[SqlValue]] selectT = ask >>= \c -> liftIO $ quickQuery c "select * from COUNTER order by COUNT" [] updateT :: IConnection c => ReaderT c IO Integer updateT = ask >>= \c -> liftIO $ run c "update COUNTER set count = count + 1" [] deleteT :: IConnection c => ReaderT c IO Integer deleteT = ask >>= \c -> liftIO $ run c "delete from COUNTER" [] commitT :: IConnection c => ReaderT c IO () commitT = ask >>= \c -> liftIO $ commit c
簡潔とは言えないですが、パターンはありますね。
ask >>= \c -> liftIO $
の後に続けてDB操作を記述すればいいです。
あと型の宣言は必須。これがないと型が曖昧だとエラーを吐かれます。
proc :: IO [[SqlValue]] proc = connectPostgreSQL connectionString >>= \c -> runReaderT ( return () >> insertT >> updateT >> commitT >> selectT ) c
確かに引数を渡しまくる必要はなくなったけど、可読性が犠牲になった感じ。こんなときは関数分割。
proc2 :: IO [[SqlValue]] proc2 = connectPostgreSQL connectionString >>= runReaderT procT procT = return () >> insertT >> updateT >> commitT >> selectT
これでどうかな?