1 Applicative TypeClass
Applicative Functor
比起Functor
更為強大,以下是其部分typeclass定義
一個類型如果為Applicative
的實例(instance)的話,則他同樣會具有funtoral structure,也就是f a
中的 f
。
而和Functor
最主要多的不同在於,多了兩個方法pure
和<*>
pure
:將型別為a
的input,包進一個Applicative的結構f
中,轉換成型別為f a
的output
<*>
: 與fmap
相似,都是把函數提升(lift),使其能應用在型別具有額外結構的值中。不同的是<*>
所提升的function,本身就被包裹在f
裡f (a -> b)
同時比較fmap
跟<*>
會比較有感:
差別只在要lift的函數有沒有f
被包裹起來。
2 Applicative functor laws
但一個型別就算其實做了pure
以及<*>
成為了Applicative
的instance了,也還不能夠說是一個Applicative
除了實作pure
以及<*>
外,還需要滿足4個Applicative functor laws
因為在Haskell中,你定義完instance Applicative XXX where...
後,並不會幫你檢查是否滿足這些Applicative functor laws
,因此這部分需要自己去確保。
3 一些使用情境
- 將普通函數
(a -> b -> c ->...)
,應用在多個有functoral structure/context的值f a, fb, ...
時
|
|
從type signature中可以看到,fmap
無法將f (a -> b)
應用於 f a,f b
這時就可以使用<*>
搭配<$>
來達成需求了
- 多個運算間沒有相依關係時12(<*>) :: Applicative f => f (a -> b) -> f a -> f b(>>=) :: Monad m => m a -> (a -> m b) -> m b
從type signature中可以看到,>>=
所串聯的運算式有相依關係的,下一個運算的依賴上一個運算的結果。
也就是說Monad >>=
的運算其實是相依(Dependency),但這也是他彈性的部分,因為我們可以針對運算的返回值再進一步的操作。
而Applicative <*>
的運算是不相依(Independency)。因為少了相依性,對比起來使用Applicative可以寫出更乾淨的代碼。
4
前面有個例子(+) <$> (Just 1) <*> (Just 2)
,不過Control.Applicative
有提供我們一些方便的工具來做同樣的事情
因此可改寫成
視覺上看起來有比較簡潔了
而liftA
liftA2
lift3
的差別只在其所要lift的函數的參數個數而已
此外,在閱讀或撰寫上,使用<$>..<*>..<*>
或許對部分人來說比較不直覺的。
這部分可以使用ApplicativeDo,使得可以跟以往使用do notation那般由上到下的書寫方式
因為expression並不會相互依賴,因此會被轉換成f <$> expr1 <*> expr2 <*> expr3
,其實就還是一樣的東西。
5 參考資料及更詳盡的內容
GHC.Base#Applicative
Haskell/Applicative functors - Wikibooks, open books for an open world
ApplicativeDo – GHC