haskell - 处理一个 monad 调用另一个 monad 的干净方法是什么?

标签 haskell monads monad-transformers

这是一个将单子(monad)粘合在一起的问题。不是以堆栈形式,而是以需要解开一个 monad 才能在另一个 monad 中运行操作的形式。

两个域:博客和应用程序。但是,请记住,应用程序域将以与当前调用 Weblog 相同的方式调用其他域。两者都有自己的 monad 堆栈。两者都跟踪自己的状态。

newtype WeblogM a = WeblogM (ReaderT Weblog (ErrorT WeblogError IO) a)
    deriving (Monad, MonadIO, Reader.MonadReader Weblog, Error.MonadError WeblogError)

newtype AppM a = AppM (ReaderT App (EitherT AppError IO) a)
    deriving ( Functor, Applicative, Monad
             , MonadReader App, MonadError AppError)

为了在 AppM 函数内运行 WeblogM 操作,我发现我必须解开 WeblogM 并重新包装它,使用这样的函数:

runWeblogHere :: forall a. Weblog.Weblog -> Weblog.WeblogM a -> AppM a
runWeblogHere weblog action =
    runIO (left . WeblogError) (Weblog.runWeblog weblog action)

runIO :: (e -> EitherT AppError IO a) -> IO (Either e a) -> AppM a
runIO handler = AppM . lift . handleT handler . EitherT

但是,这确实使我的实际直通操作变得非常简单:

getPage :: Weblog.PageId -> AppM Weblog.WikiPage
getPage pageid = do
    App{weblog} <- ask
    runWeblogHere weblog $ Weblog.getWikiPage pageid

这已经让我很困扰,因为我已经知道我有其他单子(monad)库将插入到 AppM 架构中,并且我担心编写 runXHere 方法,对于其中每一个来说,这实际上都是样板。

我建议创建一个 MonadWeblog 类来对应于 WeblogM,就像 MonadReader 对应于 一样读者T。这对我更有吸引力,因为我可以开始将 monad 胶水隔离到我的 MonadWeblog 实例中(或者实际上是 MonadX)。

最佳答案

如果我们忽略新类型,并将两个错误转换器转换为 ExceptT,则两个 monad 堆栈共享相似的结构:

import Control.Monad
import Control.Monad.Trans.Except (ExceptT, catchE)
import Control.Monad.Trans.Reader

type M env err r = ReaderT env (ExceptT err IO) r

使用withReaderTmapReaderT函数,我们可以定义:

changeMonad :: (env' -> env) 
            -> (err -> ExceptT err' IO r) 
            -> M env err r 
            -> M env' err' r 
changeMonad envLens handler = withReaderT envLens . mapReaderT (flip catchE handler)  

编辑:为了简化新类型的包装和展开,我们可以将它们设为 Wrapped 的实例。来自 lens 库,并定义更通用的转换函数:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}

newtype N1 r = N1 { getN1 :: M (Int,Int) String r }

$(makeWrapped ''N1)

--instance Wrapped (N1 r) where
--  type Unwrapped (N1 r) = M (Int,Int) String r 
--  _Wrapped' = iso getN1 N1

newtype N2 r = N2 { getN2 :: M Int Char r }

$(makeWrapped ''N2)

changeMonad' :: (Wrapped (n1 r), 
                 Unwrapped (n1 r) ~ M env' err' r, 
                 Wrapped (n2 r), 
                 Unwrapped (n2 r) ~ M env err r) 
             => (env' -> env)
             -> (err -> ExceptT err' IO r) 
             -> n2 r 
             -> n1 r  
changeMonad' envLens handler = 
     view _Unwrapped' . changeMonad envLens handler . view _Wrapped'

changeN2N1 :: N2 r -> N1 r
changeN2N1 = changeMonad' fst (\c -> throwE [c]) 

Wrapped 是一个类型类,它表示:“我实际上是一个新类型,这是添加/删除新类型构造函数的通用方法”。

如果 lens 依赖性太重,newtype包提供类似的功能。

关于haskell - 处理一个 monad 调用另一个 monad 的干净方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29901094/

相关文章:

haskell - 理解 Monad 变形金刚的困难

haskell - 在 snap 中使用 reader monad(或者在 snap 中使用 monad 转换器)

scala - 如何将 scalaz.EitherT.fromEither 应用于 scalaz.\/?

haskell - 在变压器堆栈中展开 STT 单子(monad)?

haskell - 仿函数链是如何完成的

Haskell:运行两个单子(monad),保留第一个的结果

Scala 简化了嵌套的 monad

haskell - cabal zlib 安装失败

haskell - 为什么有时可以从右侧折叠无限列表?

haskell - STM单子(monad)问题