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

时间:2015-05-23 21:23:35

标签: haskell monads monad-transformers

我得到这样的错误:

假设我有一个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实例。

2 个答案:

答案 0 :(得分:13)

MonadReader类是使用FunctionalDependencies扩展名定义的,它允许声明如

class Monad m => MonadReader r m | m -> r where
    ...

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

解决方法是编写像

这样的函数
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。这种方法可以称为“显式提升”,它使这些函数特定于App类型,因此组合性较差。

答案 1 :(得分:5)

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

{-# 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之类的东西会更好。