如何定义函数类型,它们将是monadic,IO和pure

时间:2015-06-26 08:05:20

标签: haskell

(相关问题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
    ....

1 个答案:

答案 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 Backends.

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.