2014-09-24 204 views
5

我試圖用foldr實現filterM函數,但接收到一個我不明白爲什麼的錯誤。我不明白爲什麼不能Haskell推斷這種類型

我的實現(它只是爲了解,我知道我應該使用做記號...):

filterM :: (Monad m) => (a -> (m Bool)) -> [a] -> m [a] 
filterM f list = foldr foldFn (return []) list 
    where 
     foldFn :: (Monad m) => a -> m [a] -> m [a] 
     foldFn x acc = let 
      m = f x 
      in acc >>= 
       \l -> m >>= 
        \b -> (if b == True then return (x:l) else return l) 

我收到以下錯誤

Could not deduce (a ~ a1) 
from the context (Monad m) 
    bound by the type signature for 
      filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] 
    at wadler.hs:124:12-55 
or from (Monad m1) 
    bound by the type signature for 
      ff :: Monad m1 => a1 -> m1 [a1] -> m1 [a1] 
    at wadler.hs:127:23-54 
    `a' is a rigid type variable bound by 
     the type signature for 
     filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] 
     at wadler.hs:124:12 
    `a1' is a rigid type variable bound by 
     the type signature for ff :: Monad m1 => a1 -> m1 [a1] -> m1 [a1] 
     at wadler.hs:127:23 
In the first argument of `f', namely `x' 
In the expression: f x 
In an equation for `m': m = f x 

我不知道爲什麼鍵入一個不能推導出因爲foldr相似的類型是預先foldr :: (a -> b -> b) -> b -> [a] -> b

謝謝, 亞歷

+4

您確定這是正確的代碼嗎?我沒有範圍內的'xs'。 – bheklilr 2014-09-24 15:16:02

+0

如果使用由外部模式引入的變量,則不應將類型簽名添加到本地函數。如果你想這樣做,你必須*使用'ScopedTypeVariables'擴展名並量化外簽名中的變量。 – Bakuriu 2014-09-24 15:26:45

回答

10

我相信,你的意思是

filterM f list = foldr foldFn (return []) list 

而非xs末,所以我會假設前進。

您在這裏遇到的問題是foldFn的類型簽名中的類型變量與filterM的類型簽名中的類型變量完全分離。這真的等同於說

filterM :: (Monad m) => (a -> (m Bool)) -> [a] -> m [a] 
filterM f list = foldr foldFn (return []) list 
    where 
     foldFn :: (Monad m1) => a1 -> m1 [a1] -> m1 [a1] 
     foldFn x acc = let 
      m = f x 
      in acc >>= 
       \l -> m >>= 
        \b -> (if b == True then return (x:l) else return l) 

但是,你在foldFn的定義,它說,m1必須是精確的相同m從上面使用f。您不希望foldFn的類型在任何Monad上工作,只有f正在使用的Monad。這是一個微妙的差異,一開始就很難找到。它也適用於這兩個簽名中的ab之間的差異。你可以做的僅僅是刪除foldFn的類型簽名,而GHC可以正確地推斷出foldFn的類型,但是如果不起作用,你可以使用ScopedTypeVariables擴展名。我不建議在這種情況下使用它,但有些時候,它是非常有用的,甚至必要的:

{-# LANGUAGE ScopedTypeVariables #-} 

filterM :: forall m a. (Monad m) => (a -> m Bool) -> [a] -> m [a] 
filterM f = foldr foldFn (return []) 
    where 
     foldFn :: (Monad m) => a -> m [a] -> m [a] 
     foldFn x acc = do 
      l <- acc 
      b <- f x 
      return (if b then x:l else l) 
兩種類型簽名是指同一類型的變量

而且現在ma。我還讓hlint告訴我可以對代碼進行一些改進,例如將return從最後一行的if語句中移出,使用do表示法foldFn和eta-reducing filterM使list變量隱含,並刪除一些不必要的括號。

+1

我不同意;我會*總是推薦使用'ScopedTypeVariables'擴展名。能夠爲本地定義提供類型簽名(並且實際上能夠使該類型簽名儘可能具體)對編寫和理解代碼非常有幫助。 – dfeuer 2014-09-24 18:03:27

+0

'ScopedTypeVariables'還允許對模式變量進行類型簽名,即使您不瞭解整個函數的類型,它也可以幫助您開始指定類型,並從類型檢查器中獲取更好的錯誤消息。我認爲這是一種特殊的擴展 - 對新手和高級用戶都很有用。 – dfeuer 2014-09-24 18:06:55

+0

@dfeuer在這種情況下,可以在不使用'ScopedTypeVariables'的情況下擁有本地類型簽名,您只需將'f'作爲'foldFn'的額外參數傳入,以便'foldFn'不是閉包[看到這裏](https://gist.github.com/bheklilr/db3c9e4a85340fbeba34)。無論如何,我通常更喜歡這種風格,如果我後來發現我需要重新使用'foldFn',我做的更少的更改。 – bheklilr 2014-09-24 18:08:02

相關問題