0

所以我被告知(->) 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

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

4

2 回答 2

3

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

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

这样您就可以同时评估foo testOne "there"foo testTwo "mate"

不幸的是,不存在这样的功能。库mtl的工作是抽象底层具体类型的选择。两者(->) StringReader String = ReaderT String Identity都是遵守MonadandMonadReader法则的具体类型,但这只能保证你有一个return, >>=, ask, reader, 和local(and <$>, <*>, pure) 的接口。

这既限制又有用!

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

有用:您可以使用库公开受 约束的值MonadReader,知道用户只能通过MonadReader界面使用它们。这很好,因为您可以使用这个技巧来确保用户没有做任何不妥当的事情,比如在他们自己的环境 ( r's) 中提前运行您的值。

于 2015-12-27T16:24:58.327 回答
2

runReader :: Reader r a -> r -> a专门针对Reader您要避免的新类型。由于testTwo它只是一个函数,因此您只需使用testTwo "mate".

如果您想要一种通用的运行方式MonadReader,您可以为此定义自己的类型类。大约像这样(未经测试):

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

然后runReader' testOne并且runReader' testTwo会起作用。有关此处使用的说明,请参阅“关联数据和类型系列”type

于 2015-12-27T16:09:03.273 回答