为什么这段代码需要Monad约束?

时间:2014-08-29 10:21:57

标签: haskell

在使用monad变换器构建monad堆栈来编写库时,我遇到了一个关于它的行为的问题。

以下代码无法通过类型检查程序:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Foo (FooM, runFooM, foo) where

import Control.Applicative
import Control.Monad.Reader

newtype FooM m a = FooM { runFooM :: ReaderT Int m a }
  deriving (Functor, Applicative, Monad, MonadReader Int)

foo :: FooM m Int
foo = do
  x <- ask
  return x

错误是:

$ ghc foo.hs
[1 of 1] Compiling Foo              ( foo.hs, foo.o )

foo.hs:12:3:
    No instance for (Monad m) arising from a do statement
    Possible fix:
      add (Monad m) to the context of
        the type signature for foo :: FooM m Int
    In a stmt of a 'do' block: x <- ask
    In the expression:
      do { x <- ask;
           return x }
    In an equation for ‘foo’:
        foo
          = do { x <- ask;
                 return x }

修正很容易,因为GHC建议,只需在Monad函数中添加foo约束:

foo :: Monad m => FooM m Int
foo = do
  x <- ask
  return x

但是在这里,仅foo函数ask的{​​{1}}值才能提供其FooM值,而且它已经是(自动派生的)Int实例。 因此,我认为MonadReader不需要Monad约束。

我想这与monad变换器的实现有关(我使用mlt==2.2.1), 但我无法弄清楚具体原因。 我可能会错过一些明显的东西。 你能解释一下为什么它没有通过检查器吗?

感谢。

3 个答案:

答案 0 :(得分:10)

这是因为Monad的{​​{1}}个实例被定义为

ReaderT

即。只有当内容instance Monad m => Monad (ReaderT r m) ReaderT r m的实例时,类型Monad才是m的实例。这就是为什么在使用Monad m Monad实例(ReaderT类型通过派生机制使用)时,您无法获得无约束FooM的原因。

答案 1 :(得分:5)

returns typeMonad m => a -> m a,因此需要约束。

答案 2 :(得分:5)

根据monad法律foo ≡ ask,它确实可以在没有Monad约束的情况下工作。但是如果你不需要Monad,那么GHC很难根据这些法则进行简化,是吗?在类型检查代码之前,当然不是。你写的是

的语法糖
foo = ask >>= \x -> return x

需要(>>=) :: Monad (FooM m) => FooM m Int -> (Int->FooM m Int) -> FooM m Intreturn :: Monad (FooM m) => Int->FooM m Int

同样,>>= return对正确的monad没有任何作用,但对于非monad,它甚至没有定义,因此不能被忽略。