我只是设法了解了类MonadReader
class Monad m => MonadReader r m | m -> r where
...
在阅读Haskell中的函数依赖文档之后,现在我可以理解,| m -> r
指定类型变量r
由m
唯一地确定。基于到目前为止我所见过的MonadReader的几个典型实例,我认为这一要求是合理的(例如Reader
),但在我看来,即使没有这种功能依赖性,我们仍然可以定义Reader
之类的实例条款。
我的问题是,为什么在MonadReader的定义中需要功能依赖?从某种意义上说,这在定义MonadReader时在功能上是必需的,否则无法正确定义MonadReader,或者这仅仅是限制MonadReader使用方式的限制,以便MonadReader的实例都能以某种预期的方式运行? / p>
答案 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
这是因为在没有资助者的情况下,编译器无法推断x
是Int
。毕竟单子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