为什么定义 MonadReader 需要函数依赖?

Why is FunctionalDependency needed for defining MonadReader?

我刚刚理解了 class MonadReader

的定义
class Monad m => MonadReader r m | m -> r where
...

看了Haskell中Functional Dependency的文档,现在明白了| m -> r指定类型变量rm唯一决定。基于我目前看到的几个典型的 MonadReader 实例(例如 Reader),我认为这个要求是合理的,但在我看来,即使没有这种功能依赖,我们仍然可以定义像 Reader 这样的实例条款。

我的问题是为什么在MonadReader的定义中需要函数依赖?从某种意义上说,如果没有它就无法正确定义 MonadReader,这对于定义 MonadReader 在功能上是否必要,或者它仅仅是限制如何使用 MonadReader 的方式的限制,以便 MonadReader 的实例都以某种预期的方式运行?

需要以更方便用户的方式进行类型推断。

例如,如果没有 fundep,这将无法编译:

action :: ReaderT Int IO ()
action = do
  x <- ask
  liftIO $ print x

要进行上述编译,我们需要编写

action :: ReadertT Int IO ()
action = do
  x <- ask :: ReadertT Int IO Int
  liftIO $ print x

这是因为,如果没有 fundep,编译器无法推断出 x 是一个 Int。毕竟一个 monad ReadertT Int IO 可能有多个实例

instance MonadReader Int (ReaderT Int IO) where
   ask = ReaderT (\i -> return i)
instance MonadReader Bool (ReaderT Int IO) where
   ask = ReaderT (\i -> return (i != 0))
instance MonadReader String (ReaderT Int IO) where
   ask = ReaderT (\i -> return (show i))
-- etc.

所以程序员必须提供一些强制 x :: Int 的注释,否则代码是不明确的。

我认为混淆的根源在于

的定义
class Monad m => MonadReader r m | m -> r where
  {- ... -}

隐式假定 m 包含 r 本身(对于常见实例)。让我使用 Reader 的较轻定义作为

newtype Reader r a = Reader {runReader :: r -> a}

选择 r 参数后,您可以轻松地为 Reader r 定义 monad 实例。这意味着在类型 class 定义中 m 应该替代 Reader r。因此,请看一下表达式最终如何:

instance MonadReader r (Reader r) where -- hey!! r is duplicated now
  {- ... -}                             -- The functional dependecy becomes Reader r -> r which makes sense

但是我们为什么需要这个?。查看 MonadReader class.

ask 的定义
class Monad m => MonadReader r m | m -> r where
  ask :: m r -- r and m are polymorphic here
  {- ... -}

没有 fun-dep,没有什么能阻止我定义 ask 以 return 与状态不同的类型。更重要的是,我可以为我的类型定义许多 monad reader 实例。例如,这将是没有 func-dep

的有效定义
instance MonadReader Bool (Reader r) where
--                   ^^^^         ^
--                   |            |- This is state type in the user defined newtype 
--                   |- this is the state type in the type class definition
  ask :: Reader r Bool
  ask = Reader (\_ -> True) -- the function that returns True constantly
  {- ... -}                             
instance MonadReader String (Reader r) where
--                   ^^^^^^         ^
--                   |              |- This is read-state type in the user defined newtype 
--                   |- this is the read-state type in the type class definition
  ask :: Reader r String
  ask = Reader (\_ -> "ThisIsBroken") -- the function that returns "ThisIsBroken" constantly
  {- ... -}                             

因此,如果我有一个值 val :: ReaderT Int IO Doubleask 的结果会是什么?我们需要指定一个类型签名如下

val :: Reader Int Double
val = do
  r <- ask :: Reader Int String
  liftIO $ putStrLn r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
"ThisIsBroken"
1.0

val :: Reader Int Double
val = do
  r <- ask :: Reader Int Bool
  liftIO $ print r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
True
1.0

除了毫无意义之外,一遍又一遍地指定类型是不方便的。

作为使用 ReaderT 的实际定义的结论。当你有类似 val :: ReaderT String IO Int 的东西时,功能依赖性说 这样的类型可能只有一个 MonadReader typeclass 的实例,它被定义为将 String 用作 r

的那个

这不是一个真正的答案,但对于评论来说太长了。您是正确的,可以在没有基金的情况下定义 MonadReader class。特别是,每个方法的类型签名决定了每个 class 参数。定义一个更细的层次结构是很有可能的。

class MonadReaderA r m where
  askA :: m r
  askA = readerA id

  readerA :: (r -> a) -> m a
  readerA f = f <$> askA

-- This effect is somewhat different in
-- character and requires special lifting.
class MonadReaderA r m => MonadReaderB r m where
  localB :: (r -> r) -> m a -> m a

class MonadReaderB r m
  => MonadReader r m | m -> r

ask :: MonadReader r m => m r
ask = askA

reader
  :: MonadReader r m
  => (r -> a) -> m a
reader = readerA

local
  :: MonadReader r m
  => (r -> r) -> m a -> m a
local = localB

这种方法的主要问题是用户必须编写一堆实例。