(相关问题Select instance behavior at runtime)
我想定义一种后端(可重用为独立的api),然后提供多个实现,并能够在运行时选择一个。
我的申请就像
data AppData =
AppData { ...
, backend :: Backend
}
为简单起见,请使用我们的后端(概念代码)
data Backend key value =
Backend { get :: [(key, value)]
, cud :: key -> Maybe value -> () -- Create Update Delete
}
现在定义Backend
类型的正确/推荐方法是什么?
我认为它将是monadic(然后是Monad m
),但也是IO
(然后是MonadIO m
),但也是纯粹的(然后我们需要-> ()
更改-> Backend key value
等等......
我怀疑,下一次尝试是monadic,IO
和纯粹,但可能是过度工程
data Backend m k v =
Backend { get :: MonadIO m => m [(k, v)]
, cud :: MonadIO m => k -> Maybe v -> Backend m k v
}
MonadIO
是强制限制,cud
上的返回不可变版本是多余的(在大多数情况下?)m
。
抽象它的正确/推荐方法是什么?
谢谢!
定义了我的Backend
API,我的意图或多或少地用作概念代码
main = do
configuration <- getAppConfiguration
selectedBackend <- case preferedBackend configuration of
"mongoDB" -> MongoDBBackend.makeBackend
"sqlite" -> SqliteBackend.makeBackend
"volatile" -> PureDataMapBackend.makeBackend
...
appData <- makeAppData configuration selectedBackend
....
答案 0 :(得分:7)
If you need a non-IO
backend as well, then I'd suggest to parametrize the data type by the monad in which its operations run. As pointed out by @Cactus, there is no need to add constraints to the data type itself. It doesn't help anything, it just makes things complicated. Instead, these constraints will be at functions that create various Backend
s.
Also, while it might be possible to vary the return type of the update function, using such a function would be just hell, as the application would basically need to cover all (both) cases whenever using the function. So instead I'd suggest to keep the result simple, just monadic. And for the pure backend you can just run the application within the State
monad, for example:
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State
data Backend m key value = Backend
{ beGet :: m [(key, value)]
, beCud :: key -> Maybe value -> m () -- Create Update Delete
}
pureBackend :: (MonadState [(k, v)] m, Eq k) => Backend m k v
pureBackend = Backend get pureCud
where
filterOut k = filter ((/= k) . fst)
pureCud k Nothing = modify (filterOut k)
pureCud k (Just v) = modify (((k, v) :) . filterOut k)
-- other backends ...
This means that AppData
and whoever else uses Backend
needs to be parametrized by the monad as well.