正如评论中提到的,经常需要在代码和数据库实现之间进行一些抽象。通过为 DB Monad 定义一个类,您可以获得与免费 monad 相同的抽象(我在这里采取了一些自由):
class (Monad m) => MonadImageDB m where
indexImage :: (ImageId, UTCTime) -> Exif -> Thumbnail -> m SaveResult
removeImage :: ImageId -> m DeleteResult
如果您的代码是针对MonadImageDB m =>
而不是紧密耦合到编写的DBM
,您将能够在不修改代码的情况下换出数据库和错误处理。
你为什么要使用免费的呢?因为它“尽可能地释放解释器”,这意味着解释器只致力于提供一个 monad,而不是其他任何东西。这意味着您尽可能不受约束地编写 monad 实例来配合您的代码。请注意,对于免费的 monad,您无需为 编写自己的实例Monad
,而是免费获得它。你会写类似的东西
data DBActionF next =
SaveDocument RawDocument ( next)
| GetDocuments DocumentFilter ([RawDocument] -> next)
| GetDocumentStats ([(DocId, DocumentStats)] -> next)
派生,并从现有实例中Functor DBActionF
获取 monad 实例for 。Free DBActionF
Functor f => Monad (Free f)
对于您的示例,它应该是:
data ImageActionF next =
IndexImage (ImageId, UTCTime) Exif Thumbnail (SaveResult -> next)
| RemoveImage ImageId (DeleteResult -> next)
您还可以获得类型类的“尽可能多地释放解释器”的属性。m
如果您对类型类没有其他约束MonadImageDB
,并且所有MonadImageDB
的方法都可以是 a 的构造函数Functor
,那么您将获得相同的属性。你可以通过实现来看到这一点instance MonadImageDB (Free ImageActionF)
。
如果您打算将代码与与其他 monad 的交互混合在一起,您可以免费获得 monad 转换器而不是 monad。
选择
你不必选择。您可以在表示之间来回转换。此示例说明如何对返回零、一个或两个结果的零、一个或两个参数的操作执行此操作。首先,一些样板文件
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Free
我们有一个类型类
class Monad m => MonadAddDel m where
add :: String -> m Int
del :: Int -> m ()
set :: Int -> String -> m ()
add2 :: String -> String -> m (Int, Int)
nop :: m ()
和等效的函子表示
data AddDelF next
= Add String ( Int -> next)
| Del Int ( next)
| Set Int String ( next)
| Add2 String String (Int -> Int -> next)
| Nop ( next)
deriving (Functor)
从自由表示转换为类型类替换Pure
为return
、、等。Free
>>=
Add
add
run :: MonadAddDel m => Free AddDelF a -> m a
run (Pure a) = return a
run (Free (Add x next)) = add x >>= run . next
run (Free (Del id next)) = del id >> run next
run (Free (Set id x next)) = set id x >> run next
run (Free (Add2 x y next)) = add2 x y >>= \ids -> run (next (fst ids) (snd ids))
run (Free (Nop next)) = nop >> run next
表示的MonadAddDel
实例next
使用 为构造函数的参数构建函数Pure
。
instance MonadAddDel (Free AddDelF) where
add x = Free . (Add x ) $ Pure
del id = Free . (Del id ) $ Pure ()
set id x = Free . (Set id x) $ Pure ()
add2 x y = Free . (Add2 x y) $ \id1 id2 -> Pure (id1, id2)
nop = Free . Nop $ Pure ()
(这两者都有我们可以为生产代码提取的模式,一般地编写这些模式的困难部分是处理不同数量的输入和结果参数)
针对类型类的编码仅使用MonadAddDel m =>
约束,例如:
example1 :: MonadAddDel m => m ()
example1 = do
id <- add "Hi"
del id
nop
(id3, id4) <- add2 "Hello" "World"
set id4 "Again"
除了我从免费获得的实例之外,我懒得写另一个实例,而且除了使用类型类MonadAddDel
之外,我也懒得做一个例子。MonadAddDel
如果您喜欢运行示例代码,这里足以看到示例解释一次(将类型类表示转换为自由表示),然后再次将自由表示转换回类型类表示。同样,我懒得写两次代码。
debugInterpreter :: Free AddDelF a -> IO a
debugInterpreter = go 0
where
go n (Pure a) = return a
go n (Free (Add x next)) =
do
print $ "Adding " ++ x ++ " with id " ++ show n
go (n+1) (next n)
go n (Free (Del id next)) =
do
print $ "Deleting " ++ show id
go n next
go n (Free (Set id x next)) =
do
print $ "Setting " ++ show id ++ " to " ++ show x
go n next
go n (Free (Add2 x y next)) =
do
print $ "Adding " ++ x ++ " with id " ++ show n ++ " and " ++ y ++ " with id " ++ show (n+1)
go (n+2) (next n (n+1))
go n (Free (Nop next)) =
do
print "Nop"
go n next
main =
do
debugInterpreter example1
debugInterpreter . run $ example1