所以我被告知(->) 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
库的工作是抽象底层具体类型的选择。(->) String
和Reader String = ReaderT String Identity
都是遵循Monad
和MonadReader
定律的具体类型,但这只能保证return
、>>=
、ask
、reader
和local
(以及<$>
、<*>
和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的使用说明。