2017-04-12 65 views
2

我剛剛意識到,函數具有Monad,Functor和Applicative的實例。類型實例函數

我最常做的,當我看到一些類型類實例我不明白,是寫一些良好的輸入表達式,看看它返回:

有人可以解釋這些情況?你通常會聽到關於List和Maybe的實例,這些實例現在對我來說很自然,但我不明白函數如何成爲函子或Monad。

編輯: 好吧,這是一個有效的良好的輸入表達式無法編譯:

fmap (+) (+) 1 (+1) 1 
+1

那是你確切的代碼?第一個問題是存在解析錯誤:括號不匹配。 –

+0

是的,我修正了它 – hgiesel

+1

在那個代碼片段中,play的'Functor'實例僅僅用於列表,因爲'fmap'的第二個參數是一個列表。你有效地試圖將'(+1)'作爲第一個參數給'+',即添加一個函數。函數沒有標準的'+'超載。 – pigworker

回答

4

首先,我同意你的觀點:功能不如仿函數非常直觀,的確有時我真希望這些情況並不存在。這並不是說他們有時候並沒有用處,但是他們經常以不必要和混亂的方式使用它們。這些方法總是可以替換爲更具體的組合子(特別是Control.Arrow),或者用等效的但更具描述性的reader monad替代。

這就是說...瞭解功能仿函數,我建議你先考慮Map。在某種程度上,Map Int與數組非常類似:它包含一些可以轉換的元素(即fmap),並且您可以通過索引使用整數訪問各個元素。 Map只允許「數組」在其中存在空位,並且從整數索引推廣到任何可以排序的索引。

儘管如此,Map只是函數的具體實現:它將參數(鍵)與結果(值)相關聯。這應該很清楚地說明函數函子如何工作:它覆蓋函數的所有可能結果

這個解釋很遺憾沒有太多解釋Monad實例,因爲Map實際上並沒有一個monad(甚至是Applicative)實例。列表/數組實現的一個直接的適應確實會是不可能的......回顧:上表中,我們有

pure x ≡ [x] 
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(a,y),(a,z),(b,x),(b,y),(b,z)] 

所以合併之後,指數都不同。對於我們想要支持通用密鑰的Map,這不起作用。

然而,有一個爲列表的替代單子例如,拉鍊列表

pure x ≡ repeat x 
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(b,y)] 

注意,元素的索引將被保留。

現在這個實例實際上可以適用於Map,如果只有repeat :: a -> Map k a發生器。這是不存在的,因爲一般情況下有無數個密鑰,我們不能枚舉它們,也不能平衡這樣一個Map需要的無限樹。但是,如果我們限制密鑰類型只有有限個可能的值(如Bool),那麼,我們是很好的:

instance Applicative (Map Bool) where 
    pure x = Map.fromList [(False, x), (True, x)] 
    <*> = Map.intersectWith ($) 

現在,這也正是該功能單子作品,只是不像Map怎麼也沒有問題如果無限多個不同的參數是可能的,因爲你永遠不會試圖存儲所有的與關聯的值;相反,你總是隻能當場計算值。


,如果它不是懶洋洋地做這將是不可行的 - 這在Haskell是很難的一個問題,其實如果你FMAP在Map也恰好懶洋洋地。對於功能仿函數,fmap實際上不只是懶惰,但其結果也立即被遺忘,需要重新計算。

+0

你迭代每一個可能的輸入到一個函數? – hgiesel

+0

@hgiesel語義上,是的。在運作上,當然不是;您只需輸出一個等待用戶查詢特定輸入的函數,然後修改該特定輸入的輸出。使用暗示的「數組」類比作爲命名建議:'fmap f arrayLikeFunction = \ userRequest - > f(arrayLikeFunction userRequest)';或者使用'Map'類比:'fmap f mapLikeFunction = \ key - > f(mapLikeFunction key)'。 –

2

fmap爲功能作用在由函數產生的結果:

GHCi> :set -XTypeApplications 
GHCi> :t fmap @((->) _) 
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b 

t -> a函數的a結果通過一個a -> b功能修改。如果這聽起來很像函數組合,這是因爲它正是:

GHCi> fmap (3 *) (1 +) 4 
15 
GHCi> ((3 *) <$> (1 +)) 4 
15 
GHCi> ((3 *) . (1 +)) 4 
15 

(<*>)是有點麻煩:

GHCi> :t (<*>) @((->) _) 
(<*>) @((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b 

Applicative f => f (a -> b)爭論變得t -> (a -> b)

GHCi> :t \k f -> \x -> k x (f x) 
\k f -> \x -> k x (f x) :: (t2 -> t -> t1) -> (t2 -> t) -> t2 -> t1 

這裏:(<*>)通過使用輔助功能(t -> a類型的),以產生從所述第一,第二個參數的兩個參數(t -> a -> b類型的)的函數轉換成(的t -> b型)一個參數的函數的是一個示例寫在應用性風格,採用的函數的FunctorApplicative實例:

GHCi> ((&&) <$> (> 0) <*> (< 4)) 2 
True 

一個讀取它是「喂2(> 0)(< 4)方式,和COMBI ne結果(&&)「。它可以用更簡潔的方式寫入liftA2Control.Applicative,但我相信拼寫更有意義 - 揭示。

Applicativepure另一種方法...

GHCi> :t pure @((->) _) 
pure @((->) _) :: a -> t -> a 

...使得t -> a功能出a,別無其他的。常數函數是這樣做的唯一方法:

GHCi> pure 2 "foo" 
2 
GHCi> pure 2 42 
2 

請注意pure 2在上面的每個例子不同的類型。

鑑於上述所有情況,Monad實例令人驚訝地沒有趣味。對於額外的清晰度,讓我們來看看而不是在(>>=)

GHCi> :t (=<<) @((->) _) 
(=<<) @((->) _) :: (a -> t -> b) -> (t -> a) -> t -> b 

如果你比較這類型與(<*>)的一個,你會看到他們是相同的,不同之處在於第一個參數已經翻轉。函數實例是一個例外情況,其中ApplicativeMonad做了基本相同的事情。

值得一提的是,從joinControl.Monad可用於使用值作爲兩個參數的函數的兩個參數:

GHCi> :t join @((->) _) 
join @((->) _) :: (t -> t -> a) -> t -> a 
GHCi> join (*) 5 
25 
相關問題