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

Syd*_*ove 8 monads haskell monad-transformers

我得到这样的错误:

假设我有一个monadStack ReaderT A (ReaderT B m),每当我使用ask或者asks,我得到这样的错误:

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’
Run Code Online (Sandbox Code Playgroud)

为什么Haskell无法确定使用哪个实例?另外,我该如何解决这个问题?假设put AB相同的数据类型不是一个选项,因为我需要一个MonadReader A m实例.

bhe*_*ilr 13

所述MonadReader类被使用定义的FunctionalDependencies扩展,它允许声明等

class Monad m => MonadReader r m | m -> r where
    ...
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

然后(A, B)在实际实现中使用元组:

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

还有一个withReaderT更复杂的案例.

作为不允许堆叠ReaderT的原因的一个例子,请考虑这种情况

type App = ReaderT Int (Reader Int)
Run Code Online (Sandbox Code Playgroud)

你打电话的时候ask,Int你指的是哪个?对于像这样的案件,似乎很明显

type App = ReaderT A (Reader B)
Run Code Online (Sandbox Code Playgroud)

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

ask :: App ???
Run Code Online (Sandbox Code Playgroud)

哪里???可以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)
Run Code Online (Sandbox Code Playgroud)

但你只能拥有MonadReader A App,你也不能拥有MonadReader B App.这种方法可以称为"显式提升",它使这些功能特定于App类型,因此不太可组合.

  • @SydKerckhove所以如果你有'ReaderT B(读者A)`那么你就有了'MonadReader B`实例. (2认同)

tho*_*ron 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
Run Code Online (Sandbox Code Playgroud)

不用说,只要有可能,使用类似的东西会好得多ReaderT (A, B) IO.