为什么定义 MonadReader 需要函数依赖?
Why is FunctionalDependency needed for defining MonadReader?
我刚刚理解了 class MonadReader
的定义
class Monad m => MonadReader r m | m -> r where
...
看了Haskell中Functional Dependency的文档,现在明白了| m -> r
指定类型变量r
由m
唯一决定。基于我目前看到的几个典型的 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 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 的实例,它被定义为将 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
这种方法的主要问题是用户必须编写一堆实例。
我刚刚理解了 class MonadReader
class Monad m => MonadReader r m | m -> r where
...
看了Haskell中Functional Dependency的文档,现在明白了| m -> r
指定类型变量r
由m
唯一决定。基于我目前看到的几个典型的 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 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 的实例,它被定义为将 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
这种方法的主要问题是用户必须编写一堆实例。