Sav*_*nel 38 monads dsl haskell free-monad
我正在开发一个项目,除其他外,涉及数据库访问层.非常正常,真的.在之前的项目中,合作者鼓励我将Free Monads概念用于数据库层,所以我做到了.现在,我正在尝试在我的新项目中决定我获得了什么.
在之前的项目中,我有一个看似相似的API.
saveDocument :: RawDocument -> DBAction ()
getDocuments :: DocumentFilter -> DBAction [RawDocument]
getDocumentStats :: DBAction [(DocId, DocumentStats)]
Run Code Online (Sandbox Code Playgroud)
大约二十个这样的公共职能.为了支持它们,我有了DBAction数据结构:
data DBAction a =
SaveDocument RawDocument (DBAction a)
| GetDocuments DocumentFilter ([RawDocument] -> DBAction a)
| GetDocumentStats ([(DocId, DocumentStats)] -> DBAction a)
| Return a
Run Code Online (Sandbox Code Playgroud)
然后是monad实现:
instance Monad DBAction where
return = Return
SaveDocument doc k >>= f = SaveDocument doc (k >>= f)
GetDocuments df k >>= f = GetDocuments df (k >=> f)
Run Code Online (Sandbox Code Playgroud)
然后是口译员.然后是实现每个不同查询的原始函数.基本上,我觉得我有大量的胶水代码.
在我当前的项目中(在一个完全不同的领域),我已经为我的数据库添加了一个非常普通的monad:
newtype DBM err a = DBM (ReaderT DB (EitherT err IO) a)
deriving (Monad, MonadIO, MonadReader DB)
indexImage :: (ImageId, UTCTime) -> Exif -> Thumbnail -> DBM SaveError ()
removeImage :: DB -> ImageId -> DBM DeleteError ()
Run Code Online (Sandbox Code Playgroud)
等等.我认为,最终,我将拥有代表高级概念的"公共"函数,这些函数都在DBM上下文中运行,然后我将拥有执行SQL/Haskell粘合剂的所有函数.总的来说,这比免费monad系统感觉要好得多,因为我没有编写大量的样板代码来获得除了交换我的解释器的能力.
要么...
我是否真的通过Free Monad + Interpreter模式获得了其他东西?如果是这样,什么?
Cir*_*dec 40
正如评论中所提到的,通常需要在代码和数据库实现之间进行一些抽象.你可以通过为你的DB Monad定义一个类来获得与免费monad相同的抽象(我在这里采取了一些自由):
class (Monad m) => MonadImageDB m where
indexImage :: (ImageId, UTCTime) -> Exif -> Thumbnail -> m SaveResult
removeImage :: ImageId -> m DeleteResult
Run Code Online (Sandbox Code Playgroud)
如果您的代码是针对MonadImageDB m =>而不是紧密耦合编写的DBM,那么您将能够在不修改代码的情况下交换数据库和错误处理.
你为什么要免费使用?因为它"尽可能地释放解释器",意味着解释器只承诺提供monad,而不是其他任何东西.这意味着您尽可能不受限制地编写monad实例以使用您的代码.请注意,对于免费的monad,你没有编写自己的实例Monad,你可以免费获得它.你会写类似的东西
data DBActionF next =
SaveDocument RawDocument ( next)
| GetDocuments DocumentFilter ([RawDocument] -> next)
| GetDocumentStats ([(DocId, DocumentStats)] -> next)
Run Code Online (Sandbox Code Playgroud)
派生Functor DBActionF,并Free DBActionF从现有实例中获取monad实例Functor f => Monad (Free f).
对于你的例子,它反而是:
data ImageActionF next =
IndexImage (ImageId, UTCTime) Exif Thumbnail (SaveResult -> next)
| RemoveImage ImageId (DeleteResult -> next)
Run Code Online (Sandbox Code Playgroud)
您还可以为类型类获取属性"尽可能释放解释器".如果你没有m类型类的其他约束MonadImageDB,并且所有MonadImageDB的方法都可以是a的构造函数Functor,那么你将获得相同的属性.您可以通过实施来看到这一点instance MonadImageDB (Free ImageActionF).
如果你要将代码与其他monad的交互混合,你可以从free而不是monad获得monad变换器.
你不必选择.您可以在表示之间来回转换.此示例显示了如何对具有零个,一个或两个参数的操作执行此操作,从而返回零个,一个或两个结果.首先,一点样板
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Free
Run Code Online (Sandbox Code Playgroud)
我们有一个类型
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 ()
Run Code Online (Sandbox Code Playgroud)
和等效的函子表示
data AddDelF next
= Add String ( Int -> next)
| Del Int ( next)
| Set Int String ( next)
| Add2 String String (Int -> Int -> next)
| Nop ( next)
deriving (Functor)
Run Code Online (Sandbox Code Playgroud)
从自由表示转换到类取代的种类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
Run Code Online (Sandbox Code Playgroud)
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 ()
Run Code Online (Sandbox Code Playgroud)
(这些都有我们可以为生产代码提取的模式,一般来说编写这些文件的难点在于处理不同数量的输入和结果参数)
针对类型类的编码仅使用MonadAddDel m =>约束,例如:
example1 :: MonadAddDel m => m ()
example1 = do
id <- add "Hi"
del id
nop
(id3, id4) <- add2 "Hello" "World"
set id4 "Again"
Run Code Online (Sandbox Code Playgroud)
MonadAddDel除了我从free中得到的那个之外,我太懒了,不能写另一个实例,除了使用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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2961 次 |
| 最近记录: |