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

bic*_*885 1 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
Run Code Online (Sandbox Code Playgroud)

错误是:

$ 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 }
Run Code Online (Sandbox Code Playgroud)

修正很容易,因为GHC建议,只需添加Monad约束foo功能:

foo :: Monad m => FooM m Int
foo = do
  x <- ask
  return x
Run Code Online (Sandbox Code Playgroud)

但在这里,foo函数只askS中的FooM价值给它的Int价值,它已经是一个(自动导出)MonadReader实例.所以我认为Monad不需要约束m.

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

谢谢.

sha*_*ang 10

这是因为Monad实例for ReaderT被定义为

instance Monad m => Monad (ReaderT r m)
Run Code Online (Sandbox Code Playgroud)

ReaderT r m,Monad只有当inne r m是一个实例时,type 才是一个实例Monad.这就是为什么你m在使用(你的类型通过派生机制使用的)Monad实例时不能不受约束的原因.ReaderTFooM


Thr*_*eFx 5

returnS型Monad m => a -> m a,因此需要进行约束.


lef*_*out 5

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

foo = ask >>= \x -> return x
Run Code Online (Sandbox Code Playgroud)

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

同样,>>= return对于正确的monad,它什么都不做,但对于非monad,它甚至没有被定义,因此不能被忽略.