デーモンとして動作するプログラムを書いています。デーモンを作成するために、ユーザーは必要なクラスごとに一連の実装を提供します (そのうちの 1 つはデータベースです)。これらのクラスにはすべて関数があり、フォームの型シグネチャがありますが、StateT s IO a
クラスs
ごとに異なります。
各クラスが次のパターンに従うとします。
import Control.Monad (liftM)
import Control.Monad.State (StateT(..), get)
class Hammer h where
driveNail :: StateT h IO ()
data ClawHammer = MkClawHammer Int -- the real implementation is more complex
instance Hammer ClawHammer where
driveNail = return () -- the real implementation is more complex
-- Plus additional classes for wrenches, screwdrivers, etc.
これで、各「スロット」に対してユーザーが選択した実装を表すレコードを定義できます。
data MultiTool h = MultiTool {
hammer :: h
-- Plus additional fields for wrenches, screwdrivers, etc.
}
そして、デーモンはほとんどの作業をStateT (MultiTool h ...) IO ()
モナドで行います。
マルチツールにはハンマーが入っているので、ハンマーが必要なあらゆる場面で使用できます。言い換えれば、次のMultiTool
ようなコードを記述した場合、型はそれに含まれる任意のクラスを実装できます。
stateMap :: Monad m => (s -> t) -> (t -> s) -> StateT s m a -> StateT t m a
stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g
withHammer :: StateT h IO () -> StateT (MultiTool h) IO ()
withHammer runProgram = do
t <- get
stateMap (\h -> t {hammer=h}) hammer runProgram
instance Hammer h => Hammer (MultiTool h) where
driveNail = withHammer driveNail
しかし、withHammer
、withWrench
、withScrewdriver
などの実装は基本的に同じです。こんな感じで書けると良いのですが…
--withMember accessor runProgram = do
-- u <- get
-- stateMap (\h -> u {accessor=h}) accessor runProgram
-- instance Hammer h => Hammer (MultiTool h) where
-- driveNail = withMember hammer driveNail
しかし、もちろんそれはコンパイルされません。
私のソリューションはオブジェクト指向すぎると思います。より良い方法はありますか?モナドトランスフォーマーかな?ご提案いただきありがとうございます。