为什么定义MonadReader需要功能依赖?

时间:2019-11-20 05:14:59

标签: haskell monads ghc type-systems

我只是设法了解了类MonadReader

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

在阅读Haskell中的函数依赖文档之后,现在我可以理解,| m -> r指定类型变量rm唯一地确定。基于到目前为止我所见过的MonadReader的几个典型实例,我认为这一要求是合理的(例如Reader),但在我看来,即使没有这种功能依赖性,我们仍然可以定义Reader之类的实例条款。

我的问题是,为什么在MonadReader的定义中需要功能依赖?从某种意义上说,这在定义MonadReader时在功能上是必需的,否则无法正确定义MonadReader,或者这仅仅是限制MonadReader使用方式的限制,以便MonadReader的实例都能以某种预期的方式运行? / p>

3 个答案:

答案 0 :(得分:4)

需要使类型推断更方便用户使用。

例如,没有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

这是因为在没有资助者的情况下,编译器无法推断xInt。毕竟单子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的注释,否则代码不明确。

答案 1 :(得分:2)

这并不是真正的答案,但是评论太长了。您是正确的,可以定义MonadReader类而无需提供帮助。特别地,每种方法的类型签名确定每个类参数。定义更好的层次结构很有可能。

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

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

答案 2 :(得分:1)

我认为混淆的根源在于

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

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

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

选择r参数后,您可以轻松地为Reader r定义monad实例。这意味着在类型类定义中,m应该代替Reader r。因此,看看膨胀最终是如何发生的:

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

但是为什么我们需要这个?查看ask类中MonadReader的定义。

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

没有fun-dep,没有什么能阻止我以返回其他类型作为状态的方式定义ask。甚至,我可以为我的类型定义许多monad阅读器实例。例如,这将是没有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 Double,那将是ask的结果。我们需要如下指定类型签名

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 的一个实例,该实例被定义为使用{{1} }为String