为什么定义MonadReader需要FunctionalDependency



我刚刚理解了类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

这是因为,如果没有基础,编译器就无法推断xInt。毕竟,一个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的东西时,函数依赖关系说这样的类型可能只有一个MonadReadertypeclass的实例,它被定义为使用String作为r的实例

相关内容

  • 没有找到相关文章

最新更新