将 (->) r 理解为读取器的实例



所以我被告知(->) r是Reader monad的一个实例,但我似乎找不到任何具体的例子来说明它应该如何工作。我想使用它,而不必在阅读器中明确地包装我的一些代码

import Control.Monad.Reader
testOne :: Reader String String
testOne = do
  env <- ask
  return $ "Hello, " ++ env
testTwo :: String -> String
testTwo = do
  env <- ask
  return $ "G'day, " ++ env

运行runReader testOne "there"运行正常,但运行runReader testTwo "mate"失败得惊人,并显示以下消息:

Couldn't match type ‘String -> String’
                   with ‘ReaderT [Char] Data.Functor.Identity.Identity a’
    Expected type: Reader [Char] a
      Actual type: String -> String

那么我在这里错过了什么?

如果展开newtype Reader = ReaderT r Identity,则runReader的类型为runReader :: ReaderT r Identity -> r -> a。我认为你想要一些非常通用的东西,大致如下:

foo :: (MonadReader r m) => m a -> r -> a

以便您可以同时评估foo testOne "there"foo testTwo "mate"

不幸的是,不存在这样的功能。mtl库的工作是抽象底层具体类型的选择。(->) StringReader String = ReaderT String Identity都是遵循MonadMonadReader定律的具体类型,但这只能保证return>>=askreaderlocal(以及<$><*>pure)的接口。

这既是限制性的,也是有用的!

限制:为了"运行"由任一类型表示的计算,您需要使用适当的类型特定的API。对于(->) String,这只是调用函数(不可见的函数应用程序运算符);对于runReader,则为MonadReader

有用:您可以通过库公开受MonadReader约束的值,因为您知道用户只能在r接口中使用这些值。这很好,因为你可以使用这个技巧来确保用户不会做任何不愉快的事情,比如用他们自己的环境(runReader :: Reader r a -> r -> a)早期运行你的价值观。

Reader专门用于testTwo newtype,您希望避免这种情况。由于testTwo "mate"只是一个函数,所以只需使用MonadReader

如果您想要一种通用的方式来运行runReader' testOne,您可以为此定义自己的类型类。大致如此(未经测试):

class MonadReader r m => RunReader r m | m -> r where
  type Output m a :: *
  runReader' :: m a -> r -> Output m a
instance RunReader r ((->) r) where
  type Output ((->) r) a = a
  runReader' = ($)
instance Monad m => RunReader r (ReaderT r m) where
  type Output (ReaderT r m) a = m a
  runReader' = runReaderT
instance RunReader r m => RunReader r (MaybeT m) where
  type Output (MaybeT m) a = Output m (Maybe a)
  runReader' = runMaybeT . runReader'
-- any other instances

则CCD_ 34和CCD_。请参阅"关联的数据和类型族",了解此处CCD_36的使用说明。

相关内容

  • 没有找到相关文章

最新更新