2014-08-29 42 views
1

在用monad變換器構建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 s的FooM值賦予它的Int值,它已經是一個(自動派生的)MonadReader實例。 所以我認爲Monad約束是不需要m

我想這涉及到monad變壓器(我使用mlt==2.2.1), 的實施,但我無法弄清楚確切的原因。 雖然我可能會錯過某些明顯的東西。 你能解釋爲什麼這不通過檢查?

謝謝。

回答

10

這是因爲對於ReaderTMonad實例定義爲

instance Monad m => Monad (ReaderT r m) 

即類型ReaderT r mMonad實例僅當INNEřmMonad一個實例。這就是爲什麼當使用ReaderTMonad實例(您的FooM類型通過派生機制使用)時,您不能有無約束的m

+0

啊!這是我錯過的答案。非常感謝你! – bicycle1885 2014-09-01 14:31:11

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,它甚至沒有定義,因此不能被忽略。

相關問題