如何创建一个允许IO但不是MonadIO的monad?

时间:2017-07-23 14:15:49

标签: haskell monads monad-transformers

我正在尝试创建一个只允许特定IO功能的monad。这意味着这个假设的monad不能是MonadIO,也不能允许调用liftIO

以下是我到目前为止所做的事情,但我仍然坚持Monad的{​​{1}}个实例:

AppM

1 个答案:

答案 0 :(得分:9)

如果您只想隐藏MonadIO

AppM -

我会继续并加入

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

并将data声明更改为

newtype App a = App {runApp :: ReaderT Env (LoggingT IO) a}
              deriving (Functor, Applicative, Monad, MonadReader Env,
                       , MonadLoggerIO }

因此,如果您需要App类似的操作,那么MonadIO就不是liftIO,您可以在库中提供这些操作,例如

putStrLn :: String -> App ()
putStrLn = fmap App . liftIO Prelude.putStrLn

注意:liftIO用于ReaderT Env (LoggingT IO) (),然后包含在App中,并且您不会公开完整的IO功能。

更新

关于如何实施FunctorApplicativeMonad的问题,这只是包装/解包的任务:

instance Functor App where
   fmap f = App . fmap f . runApp

instance Applicative App where
  pure = App . pure
  mf <*> mx = App (runApp mf <*> runApp mx)

instance Monad App where
  mx >>= f = App $ (runApp mx) >>= (runApp . f)

最后一行是唯一棘手的 - 如

>>= :: ReaderT Env (LoggingT IO) a -> (a -> ReaderT Env (LoggingT IO) b) -> ReaderT Env (LoggingT IO) b

mx :: App af :: a -> App b所以我们需要

runApp :: App a -> ReaderT Env (LoggingT IO) a

打开f的结果类型以在解包设置中工作 - 这在写下来时似乎非常明显,但在此之前可能会引起一些麻烦。

UPDATE2

我发现那篇文章有人把我和很长时间联系起来(但在同一个星系中)来自monad读者Ed Z. Yang - Three Monads (Logic, Prompt, Failure)