我刚刚理解了类MonadReader
的定义
class Monad m => MonadReader r m | m -> r where
...
在阅读了Haskell中的Functional Dependency文档后,现在我可以理解| m -> r
指定类型变量r
是由m
唯一决定的。基于我迄今为止看到的MonadReader的几个典型实例(例如Reader
(,我认为这个要求是合理的,但在我看来,即使没有这个函数依赖子句,我们仍然可以定义像Reader
这样的实例。
我的问题是,为什么我们在MonadReader的定义中需要函数依赖性?从某种意义上说,如果没有MonadReader就无法正确定义MonadReader,这在功能上对定义MonadRead是必要的吗?还是这只是一种限制,以限制MonadReader的使用方式,从而使MonadReader实例都以某种预期的方式运行?
需要使类型推理以更方便用户的方式工作。
例如,如果没有fundeep,这将无法编译:
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
。毕竟,一个monadReadertT 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
的注释,否则代码是不明确的。
这不是一个真正的答案,但对于注释来说太长了。您是正确的,可以在没有fundeep的情况下定义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
这种方法的主要问题是用户必须编写一堆实例。
我认为混淆的来源是的定义
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
但我们为什么需要这个?。查看MonadReader
类中ask
的定义。
class Monad m => MonadReader r m | m -> r where
ask :: m r -- r and m are polymorphic here
{- ... -}
如果没有有趣的dep,没有什么可以阻止我以返回不同类型作为状态的方式定义ask
。更重要的是,我可以为我的类型定义许多monad阅读器的实例。例如,如果没有函数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
除了毫无意义之外,一遍又一遍地指定类型也是不可信的。
作为结论使用CCD_ 24的实际定义。当你有类似val :: ReaderT String IO Int
的东西时,函数依赖关系说这样的类型可能只有一个MonadReader
typeclass的实例,它被定义为使用String
作为r
的实例