原文地址:Haskell學習-functor
什麼是Functor
functor 就是可以執行map操作的物件,functor就像是附加了語義的表示式,可以用盒子進行比喻。functor 的定義可以這樣理解:給出a對映到b的函式和裝了a的盒子,結果會返回裝了b的盒子。fmap 可以看作是一個接受一個function 和一個 functor 的函式,它把function 應用到 functor 的每一個元素(對映)。
-- Functor的定義
class Functor f where
fmap :: (a -> b) -> f a -> f b
某個型別要能進行對映操作(map over),就必須繼承Functor基類,並實現其中的fmap函式。我們來看一下幾種預設的Functor形態:
列表list,非常好理解,操作列表我們一般使用map函式,它其實就是fmap針對列表的一個具體例項,在list中它們是等價的。
-- 作為functor 的定義: instance Functor [] where fmap = map -- 例項 fmap (*2) [1,2,3] > [2,4,6]
Maybe,它是haskell中使用很廣泛的資料型別,它有 Just 值 和 Nothing 兩種情況,分別用於表示成功和失敗的情況。
-- Maybe 的 functor 定義: instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing -- 例項 fmap (*2) (Just 1) > Just 2 fmap (*2) (Nothing) > Nothing
IO,輸入與輸出,比如讀取鍵盤輸入,列印字串等
-- IO 的 Functor 定義 instance Functor IO where fmap f action = do result <- action return (f result) -- 例項 fmap ("hello! "++) getLine jeff -- 輸入名字,列印時新增“hello” > "hello! jeff"
Functor的 (->) r 形態
(->) r 其實表示的是函式結合,也就是等價於 (.)
-- 下面兩個定義是等價的,也就是 (->) r 形式下的 Functor 其實等價於 結合律
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
instance Functor ((->) r) where
fmap = (.)
-- 例項
fmap (*3) (+100) 1
> 303
(*3) . (+100) $ 1
> 303
functor law
如果某個型別遵守這兩個定律,那麼它與其他Functor對於對映方面就具有相同的性質。
fmap id = id
如果我們對 functor 做 map id,那得到的新的 functor 應該要跟原來的一樣fmap id (Just 3) > Just 3 id (Just 3) > Just 3
fmap (f . g) = fmap f . fmap g 也就是 functor 是能應用於函式結合的。
Applicative Functor
為什麼需要 Applicative Functor,什麼情況下使用它。從Functor定義我們知道,fmap函式只能對映單個盒子,但假設需要對映兩個三個,甚至是更多的盒子呢?或者是要處理返回值是函式的盒子呢?而這就是 Applicative Functor 要處理的情況。
Applicative Functor 可以看作是Functor的增加版,從定義可知,它主要包括pure 和 <*>兩個函式。
-- Applicative Functor 定義
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
- pure :: a -> f a 意思就是把普通值放到預設的context(語義)下。比如如果是list,那麼它代表的就是[ ] ,如果是Maybe,那麼它就是 Just 值 / Nothing。
(<*>) 接受一個裝有函式的 functor 跟另一個 functor, 非常類似於fmap,它就像加強版的 fmap。以applicative style 的方式來使用 applicative functors。像是 pure f <*> x <*> y <*> ... 這個函式可以吃任意多的引數。
-- 與fmap型別的對比,可以看出函式 a -> b 被裝進了盒子 f 中 (<*>) :: f (a -> b) -> f a -> f b fmap :: (a -> b) -> f a -> f b -- <*> 是左結合的,因此以下兩個表示式是相等的 pure (+) <*> Just 3 <*> Just 5 (pure (+) <*> Just 3) <*> Just 5。
(<$>) 是applicative functor 中另一個很常用的符號,它其實就是中綴版的fmap。因為結合fmap寫applicative functor更加方便。
(<$>) :: (Functor f) => (a -> b) -> f a -> f b f <$> x = fmap f x -- 用<*>實現相同的功能 pure f <*> x = fmap f x
接著看一下幾個預設的 applicative functor,繼承Applicative,必須實現 pure 和 (<*>) 函式
Maybe 型別
-- Maybe 的 Applicative 定義: instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something -- 例項 pure (+3) <*> Just 9 > Just 12 pure (+) <*> Just 3 <*> Just 5 > Just 8
列表list 也是 applicative functor,從定義可以看出使用list的Applicative style完全可以實現 list comprehension 的功能。所以 Applicative style 對於 list 而言是取代某些型別的 list comprehension 的好方式。
-- list 的定義 instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs] -- 例項 [(+3),(*2)] <*> [1,2] > [4,5,2,4] --下面表示式具有相同的功能 (*) <$> [2,5,10] <*> [8,10,11] -- Applicative style [ x * y | x <- [2,5,10], y <- [8,10,11]] -- list comprehension > [16,20,22,40,50,55,80,100,110]
IO ,下面的IO的例項,可以把 getLine 看做是一個去真實世界中拿取字串的盒子, 而 applicative functor 表示式會創造一個比較大的盒子,這個大盒子會派兩個盒子去終端拿取字串,並把結果串接起來放進自己的盒子中。
--IO 的 Applicative instance instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (f x) -- 例項 將輸入的兩個字串合併 (++) <$> getLine <*> getLine aa bb > "aabb"
Applicative Functor 的 (->) r 形態
(->) r 形態定義
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
- 用 pure 將一個值包成 applicative functor 的時候,他產生的結果永遠都會是那個值
- 將兩個 applicative functor 餵給 <*> 可以產生一個新的 applicative functor
接著綜合使用上面的知識,來看一下實際應用applicative的幾種方式。相比起functor,applicative functor要更強大和靈活。
-- 左結合形式, 第一項必須為含有函式的functor,右邊全部為functor
pure (\x y z -> x+ y +z) <*> Just 3 <*> Just 4 <*> Just 5
> Just 12
[(+3),(*2)] <*> [1,2]
> [4,5,2,4]
-- fmap(<$>) 形式,第一項為普通函式,右邊都為functor
(+) <$> Just 1 <*> Just 2
> Just 3
(\x y z -> x + y +z) <$> [1,2] <*> [2,3] <*> [4,5]
> [7,8,8,9,8,9,9,10]
-- (<$>) (->) r 形式,全部為普通函式,用單個引數呼叫執行
(\x y z -> [x,y,z]) <$> (3+) <*> (*100) <*> (`div`2) $ 2
> [5,200,1]
Applicative Functor 輔助函式
liftA2
只是applicative的套用函式而已,當然還有3個引數的版本 liftA3,而 liftA 則等價於 fmap-- 與applicative 的等價形式 liftA2 f a b = f <$> a <*> b -- 以下表示式功能一致 liftA2 (:) (Just 3) (Just [4]) (:) <$> Just 3 <*> Just [4] pure (:) <*> Just 3 <*> Just [4] > Just [3,4]
sequenceA
當套用在函式上時,sequenceA 接受裝有一堆函式的list,並回傳一個回傳list的函式。當我們有一串函式,想要將相同輸入都餵給它們並檢視結果的時候,sequenceA非常好用。
當使用在 I/O action 上的時候,sequenceA 跟 sequence 是等價的。他接受一串 I/O action 並回傳一個 I/O action,這個 I/O action 會計算 list 中的每一個 I/O action,並把結果放在一個 list 中-- 以下是兩種實現sequenceA功能一致的函式 sequenceA (x:xs) = (:) <$> x <*> sequenceA xs sequenceA = foldr (liftA2 (:)) (pure []) sequenceA [Just 3, Just 2, Just 1] > Just [3,2,1] -- 將list組合成所有可能的組合 sequenceA [[1,2,3],[4,5,6]] > [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] sequenceA [(>4),(<10),odd] 7 map (\f -> f 7) [(>4),(<10),odd] > [True,True,True] -- and接受一串Bool,並在所有值都為True時才返回True and $ sequenceA [(>4),(<10),odd] 7 and $ map (\f -> f 7) [(>4),(<10),odd] > True