2016-04-10 89 views
2

我剛剛瞭解monad,並且我一直在嘗試在Control.Monad中實現很多功能。我剛到ap,但我無法完成工作。我做了一個函數almostAp :: Monad m => m (a -> b) -> m a -> m (m b),我試圖用我製作的另一個函數flatten :: Monad m => m (m b) -> m b來編寫它。問題是,當我嘗試使用ap = flatten . almostAp,我得到嘗試撰寫monadic函數時出錯

Occurs check: cannot construct the infinite type: m ~ (->) (m a) 
    Expected type: m (a -> b) -> m a -> m a -> m b 
    Actual type: m (a -> b) -> m a -> m (m b) 
In the second argument of ‘(.)’, namely ‘almostAp’ 
In the expression: (flatten . almostAp)` 

但是,(flatten .)有根據ghci中鍵入Monad m => (a -> m (m b)) -> a -> m b,那麼爲什麼會出現這種情況?

下面是函數定義(我知道我可以清理它們與=<<和仿函數法):

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
almostAp = (flip (\x -> fmap ($x))) . (fmap (flip (>>=))) . (fmap (return .)) 

flatten :: Monad m => m (m a) -> m a 
flatten = (>>= id) 
+4

你似乎非常熱衷於無點的風格,這使得你的代碼很難閱讀(而且,我可以想象,也很難寫!)不要害怕'let'綁定或'做'符號。 –

+2

「'let'走出無點風格:只用它'在哪裏'它有道理,而且你會'做得更好' – Cactus

回答

5

你是讓你的類型的錯誤,因爲你試圖組成一個一個參數功能flatten(順便說一下,通常以名稱join)與雙參數函數almostAp(.) :: (b -> c) -> (a -> b) -> (a -> c)適用於右側的功能是單參數功能的情況。不幸的是,錯誤信息並不是很有幫助。

(.).(.)(發音爲 「 -dot- 」,或 「胸部」),你想要做什麼:

ghci> let ap = ((.).(.)) flatten almostAp 
ghci> :t ap 
ap :: Monad m => m (a -> b) -> m a -> m b 

ap可以更簡單地do符號來實現。你的版本比這更容易理解嗎?

ap mf mx = do 
    f <- mf 
    x <- mx 
    return (f x) 

do符號僅僅是>>=語法糖。下面是它的外觀沒有它(雖然我更喜歡do版本):

ap mf mx = mf >>= \f -> fmap f mx 
+2

'。(。)。(。)'是我腦海中的」貓頭鷹眼睛「,它似乎更多...家庭友好。 – dfeuer

3

但是,(flatten .)已按ghci中鍵入Monad m => (a -> m (m b)) -> a -> m b,那麼爲什麼會出現這種情況?

確實如此。試想一下:

flatten :: Monad m => m (m a) -> m a 

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 

因此,(flatten .)類型是:

flatten  :: Monad m => m (m a) -> m a -- renaming a to b 
          |  | | | 
          ------- --- 
          |  | 
(.)   ::    (b -> c) -> (a -> b) -> a -> c 
                |    | 
                -------   --- 
                |  |   | | 
(flatten .) :: Monad m =>     (a -> m (m b)) -> a -> m b 

但是,你不能申請(flatten .)almostAp,因爲類型不兼容:

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
          |  | |   | 
          ---------- -------------- 
           |    | 
           |   ------- 
           |   |  | 
(flatten .) :: Monad m => ( a  ->  m (m b)) -> a -> m b 

您預計:

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
          |    | |  | 
          ----------------- ------- 
            |    | 
            |   ------- 
            |   |  | 
(flatten .) :: Monad m => (  a   -> m (m b)) -> a -> m b 

但是,這不是currying的工作方式。 a -> b -> c類型的功能意味着a -> (b -> c)而不是(a -> b) -> c。第一個函數a -> (b -> c)需要兩個參數,ab,並返回c。第二個函數(a -> b) -> c取一個參數a -> b並返回c

那麼,你如何撰寫flattenalmostAp

(.) :: (b -> c) -> (a -> b) -> a -> c 
     |  | |  | 
     -------- -------- 
      |   | 
     flatten  +-- almostAp can't be used because it needs two arguments 
         -- but (.) only gives it one argument (the `a` in a -> b) 

我們需要一種特殊的複合算來撰寫他們:

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d 
     |  | |   | 
     -------- ------------- 
      |    | 
     flatten  almostAp 

(.:) f g x y = f (g x y) 

現在,我們可以簡單地寫flatten .: almostAp使用正常的函數組成,因爲almostAp需要兩個參數,你不能做到這一點。另一種編寫它的方法是(flatten .) . almostAp。這是因爲(.:) = (.) . (.)。閱讀以下的詳細的細節:

What does (f .) . g mean in Haskell?


事實上,類型a -> (b -> c)的函數只需要一個參數a並返回另一個功能b -> c這需要第二個參數b並返回c