2016-02-14 100 views
8

我是新來函數式編程(來自JavaScript),我很難說出兩者之間的差異,這也與我對函子與單子的理解混淆。fmap和bind之間的差異?

函子:

class Functor f where 
    fmap :: (a -> b) -> f a -> f b 

單子(簡化):

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
  • fmap接受一個函數和一個仿函數,並返回一個算符。
  • >>=需要一個函數和一個monad,並返回一個monad。

兩者之間的區別是在功能參數:

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>=需要一個返回單子一個函數參數。我知道這很重要,但我很難看出這個輕微的東西讓單子變得比仿函數強大得多。有人可以解釋嗎?

+4

這更容易看到翻轉版本的'(>> =)',['(= <<)'](https://stackoverflow.com/questions/34545818/is-monad-bind-operator-近到功能的組合物鏈連接有或functi/34561605#34561605)。使用'(g <$>):: f a - > f b',函數'g :: a - > b'不會影響'f''包裝「 - 不會改變它。用'(k = <<) :: m a -> mb',函數'k :: a - > mb'本身*創建新的''m''包裝「,所以它可以改變。 –

+0

@WillNess我可以」理解「這一點,但是我我認爲我真正的問題是我看不到'>> ='可以做到'fmap'不能做什麼,在我的腦海中它們是相同的,因爲我沒有看到一個例子,它顯示fmap是不夠的, – m0meni

+4

使用列表,嘗試使用'map'過濾掉列表中的某些元素,但不能使用'concatMap',但可以:'map(\ x- > x + 1)[1,2,3]'vs'concatMap(\ x-> [x,x + 1 | even x])[1,2,3])''。 –

回答

13

好,(<$>)fmap的別名,是一樣的(>>=)與交換的參數:

(<$>) :: (x -> y) -> b x -> b y 
(=<<) :: (x -> b y) -> b x -> b y 

不同的是現在相當清楚:與綁定功能,我們應用返回功能一個b y而不是一個y。那麼這有什麼不同?

考慮這個小例子:

foo <$> Just 3 

注意(<$>)將適用於foo3,並把結果返回到Just。換句話說,這個計算結果不能是Nothing。與此相反:

bar =<< Just 3 

這種計算可以返回Nothing。 (例如,bar x = Nothing會做到這一點。)

我們可以做的名單單子類似的事情:

foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list. 
bar =<< [Red, Yellow, Blue] -- Result can be ANY size. 

總之,隨着(<$>)(即fmap),該結果的「結構」總是與輸入相同。但與(即,(>>=)),結果的結構可能會改變。這允許有條件的執行,對輸入做出反應,還有一大堆其他事情。

+4

只是爲了完整性,Applicative可以返回'Nothing':'Nothing <*> Just 3'。不同之處在於,「管道」(即計算結構)在組成計算時固定*,在*它運行之前*。但是對於Monads,管道可以根據生成的值而改變*而「運行」。 (在IO的情況下,'3'大概被接收,例如作爲用戶的輸入)。 - * list *示例是esp。這裏很好:'(foo <$>)'保持結構(列表的長度); '([baz,quux] <*>)'會改變結構*可預測*(創建長度-6列表); Monad所有投注都關閉。 –

8

簡短的回答是,如果你能以合理的方式將m (m a)轉換爲m a那麼它就是Monad。這對所有Monad來說都是可能的,但對於Functors來說不一定是這樣。

我認爲最容易混淆的事情是所有常見的Functor例子(例如List,Maybe,IO)也是Monads。我們需要一個是Functor而不是Monad的例子。

我將使用假設的日曆程序中的示例。以下代碼定義了一個存儲與事件一起發生的一些數據及其發生的時間的函子函數。

import Data.Time.LocalTime 

data Event a = MkEvent LocalTime a 

instance Functor Event where 
    fmap f (MkEvent time a) = MkEvent time (f a) 

Event對象存儲該事件發生的時間和可以使用fmap可以改變一些額外的數據。現在,讓我們嘗試,並使其成爲單子:

instance Monad Event where 
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in 
           MkEvent <notSureWhatToPutHere> b 

,我們發現我們不能,因爲你最終將有兩個LocalTime對象。 timeA來自給定的EventtimeBEvent給出的結果f a。我們的Event類型被定義爲只有一個LocalTimetime)它發生在,因此如果沒有將兩個LocalTime合併成一個,那麼使它成爲Monad是不可能的。 (可能有些情況下,這樣做可能有意義,所以如果你真的想這樣做,你可以把它變成一個monad)。

+3

不是monad的經典/常見仿函數的一個例子是'newtype Const a b = Const a'。 – dfeuer

+3

單純律要求'純x >> = f'爲'f x',但'pure :: b - > Const a b'不能使用它的參數。 – dfeuer

+1

@dfeuer這個接縫[太簡單了,簡單](https://ncatlab.org/nlab/show/too+simple+to+be+simple)。此外,我找不到一種方法來編寫除fmap f(Const x)= Const x'以外的Functor實例。# – HEGX64

3

假設片刻IO只是Functor,而不是Monad。我們如何排序兩個動作?說,像getChar :: IO CharputChar :: Char -> IO()

我們可以嘗試使用putChar來映射getChar(執行時從stdin中讀取Char的操作)。

fmap putChar getChar :: IO (IO()) 

現在我們有一個程序,在執行時,從標準輸入讀取Char和產生程序,在執行時,該Char寫到stdout。但是我們真正需要的是一個程序,它在執行時從stdin中讀取Char,並將Char寫入stdout。因此,我們需要一個「扁平化」(在IO情況下,「序」)的功能與類型:

join :: IO (IO()) -> IO() 

Functor本身不提供此功能。但它的Monad一個功能,它具有更普遍的類型:

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

是什麼這一切都與>>=?碰巧的是,一元綁定只是一個fmapjoin組合:

:t \m f -> join (fmap f m) 
(Monad m) => m a1 -> (a1 -> m a) -> m a 

看到的差異的另一種方式是fmap從不改變映射的值的總體結構,但join(因此>>=以及)可以做到這一點。

IO行動等方面,fmap從未原因addicional讀/寫或其他效果。但是join將外部動作的內部動作的讀取/寫入排序。