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

How to create a monad which allows IO but is NOT a MonadIO?

我正在尝试创建一个只允许特定 IO 函数的 monad。这意味着这个假设的 monad 不能是 MonadIO 并且不能允许 liftIO 被调用。

这是我到目前为止所拥有的,但我仍然坚持 AppMMonad 实例:

data AppM a = AppM {unwrapAppM :: ReaderT Env (LoggingT IO) a}

instance Functor AppM where
  fmap fn appm = AppM $ fmap fn (unwrapAppM appm)


instance Applicative AppM where
  pure a = AppM $ pure a

如果您只是想隐藏 AppM

MonadIO-ness

我会继续并放入一个

{-# 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 的问题,这只是 wrapping/unwrapping:

的任务
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 的结果类型以在解包设置中工作 - 这在写下来时看起来非常明显,但在此之前可能会引起一些头痛。

更新2

我发现有人从 monad reader Ed Z. Yang 链接了很久很久(但在同一个星系)的论文 - 三个 Monad(逻辑、提示、失败)