为什么我不能将两个阅读器堆叠在一起?

Why can't I stack two readers ontop of eachother?

我收到这样的错误:

假设我有一个 monadStack ReaderT A (ReaderT B m),每当我使用 askasks 时,我都会收到这样的错误:

Types.hs:21:10:
    Couldn't match type ‘A’ with ‘B’
    arising from a functional dependency between:
      constraint ‘MonadReader B m’
        arising from the instance declaration
      instance ‘MonadReader A m2’ at Types.hs:21:10-63
    In the instance declaration for ‘MonadReader A m’

为什么 Haskell 不知道要使用哪个实例?另外,我该如何解决这个问题? 假设将 AB 放在同一数据类型中不是一个选项,因为我需要一个 MonadReader A m 实例。

MonadReader class 是使用 FunctionalDependencies 扩展定义的,它允许像

这样的声明
class Monad m => MonadReader r m | m -> r where
    ...

这意味着对于任何 monad mr 由它唯一确定。因此,您不能用一个 monad m 来确定两种不同的 r 类型。如果没有这个限制,编译器将无法对 class.

的使用进行类型检查

解决这个问题的方法是像这样编写你的函数

getA'sInt :: A -> Int
getA'sInt = undefined

getB'sString :: B -> String
getB'sString = undefined

foo :: (MonadReader A m) => m Int
foo = do
    a <- asks getA'sInt
    return $ a + 1

bar :: (MonadReader B m) => m String
bar = do
    b <- asks getB'sString
    return $ map toUpper b

然后在你的实际实现中使用一个元组(A, B)

baz :: Reader (A, B) (Int, String)
baz = do
    a <- withReader fst foo
    b <- withReader snd bar
    return (a, b)

还有一个 withReaderT 用于更复杂的情况。

作为为什么不允许堆叠ReaderT的例子,考虑一下

type App = ReaderT Int (Reader Int)

当你调用ask时,你指的是哪个Int?对于像

这样的情况,这似乎很明显
type App = ReaderT A (Reader B)

编译器应该能够找出使用哪个,但问题是这里的 ask 函数的类型是

ask :: App ???

其中 ??? 可以是 AB。您可以通过不直接使用 MonadReader 并定义特定的 askAaskB 函数来解决此问题:

type App = ReaderT A (Reader B)

askA :: App A
askA = ask

askB :: App B
askB = lift ask

baz :: App (Int, String)
baz = do
    a <- askA
    b <- askB
    return (getA'sInt a, getB'sString b)

但是你只能拥有MonadReader A App,不能同时拥有MonadReader B App。这种方法可以称为 "explicit lifting",它使这些函数特定于 App 类型,因此组合性较差。

可以使用一些扩展来完成,但重叠实例可能不是一个好主意。

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}

import Control.Monad.Reader

import Data.Functor.Identity

data A = A deriving Show
data B = B deriving Show

type SomeMonad a = ReaderT A (ReaderT B Identity) a

instance MonadReader a m => MonadReader a (ReaderT b m) where
  ask = lift ask
  local f mx = do
    b <- ask
    lift $ local f $ runReaderT mx b

main :: IO ()
main = do
  let res = runIdentity $ flip runReaderT B $ flip runReaderT A $ do
              a <- ask :: SomeMonad A
              b <- ask :: SomeMonad B
              return (a, b)
  print res

不用说,只要有可能,最好使用 ReaderT (A, B) IO.