一進門,就看到矗立在大廳中央的雕像,有點像是箭頭,又有點像是管子。臺座周圍四散著一堆各自相異,但卻都比較短的箭頭。當我隨手拿起一個箭頭想看時,雕像一側呼吸般閃滅著柔和的光,像是要我把手上的東西丟進去似的,於是我就照做了。
雕像的出口跑出另一個箭頭,跟原先的看起來很像,但其末端帶著幾個像爪子的裝飾,好像可以打開什麼東西似的。我順手多丟了幾個進雕像裡去…
當然。做為具有不變動外層的結構,而可以用函式改變內容的性質的東西,串列應該是最為人所知曉的存在了。在串列上的 fmap
,就是 map
。但請記得,當你談論的容器也只有單純的串列/陣列時,是沒什麼必要動用到 functor 這個詞彙的。
-- Haskell 語法
(<$>) @ [] :: (a -> b) -> [a] -> [b]
map (+1) [1, 2, 3] -- => [2, 3, 4] -- 把串列當做…嗯…串列
fmap (+1) [1, 2, 3] -- => [2, 3, 4] -- 把串列當做一種 Functor
嗯,既然字串就是字元的串列,那麼字串當然也是個 functor。
是的。身為另一種容器的代表,二元組也是一種 functor。而當對二元組 fmap
時,該函式只會改變二元組中後面那個元素:
-- Haskell 語法
(<$>) @ ((,) _) :: (a -> b) -> (_, a) -> (_, b)
fmap (+1) (1, 10) -- => (1, 11)
是的。一樣是改變元組裡最後的那個元素:
(<$>) @ ((,,) _ _)
:: Functor ((,,) _1 _2) =>
(a -> b) -> (_1, _2, a) -> (_1, _2, b)
fmap (+10) (1, 2, 3) -- => (1, 2, 13)
是的。如我們之前所說,Maybe 也是一種容器。而 fmap
對於裡面有值的 Just
,會用接收到的函式來更動 Just
裡面的值,並包回 Just
中。但若是 Nothing
,則如同沒有作用。而這是非常有用的性質。
-- Haskell 語法
(<$>) @ Maybe :: (a -> b) -> Maybe a -> Maybe b
fmap (+1) (Just 1) -- => Just 2
fmap (+1) Nothing -- => Nothing
是的。而且類似 Maybe 那樣,當對右值 Right
進行 fmap
時,會使用接收到的函式來改變其中的值。但完全不會去動左值 Left
裡的東西。
-- Haskell 語法
(<$>) @ Either e :: (a -> b) -> E e a -> E e b
fmap (+1) (Right 10) -- => Right 11
fmap (+1) (Left "Something went Wrong!") -- => Left "Something went Wrong!"
是的。Sum 也可以視為一種容器,而這個容器也擁有 functor 的實體。
-- Haskell 語法
(<$>) @ Sum :: (a -> b) -> Sum a -> Sum b
fmap (* 10) (Sum 2) -- => Sum {getSum = 20}
都 functor 啦,哪一個不 functor。
這些就自己試試看吧。
於是我們可以來試試看這個改變容器內容的抽象的其中一個優點了,用 fmap
升格所得的函式,是不在乎遇到什麼容器的。
-- Haskell 語法
-- map 也可以將函式升格
g = map (+1)
g [1, 2, 3] -- => [2, 3, 4]
g $ Sum 1 -- 然而只能用在串列上,在其它型別上會出錯
-- <interactive>:10:5: error:
• Data constructor not in scope: Sum :: Integer -> [b]
-- 可以用在各種 functor 上的升格函式
addOne = fmap (+1)
addOne [1, 2, 3] -- => [2, 3, 4]
addOne $ Sum 1 -- => Sum {getSum = 2}
addOne $ Just 1 -- => Just 2
addOne $ Nothing -- => Nothing
addOne $ Right 10 -- => Right 11
addOne $ Left 1 -- => Left 1
addOne $ (1, 2) -- => (1, 3)
addOne $ (1, 2, 3) -- => (1, 2, 4)
addOne $ (1, 2, 3, 4) -- => (1, 2, 3, 5)
在 GHC 8 之後的 ghci 中,你可以打開 TypeApplications
功能標籤,看查看某個函式對某個特定型別的型別定義:
-- Haskell 語法
Prelude> :set -XTypedApplications
Prelude> :t fmap @ Maybe
fmap @ Maybe :: (a -> b) -> Maybe a -> Maybe b
Prelude> :t (<$>) @ []
(<$>) @ [] :: (a -> b) -> [a] -> [b]
Prelude> :t (<$>) @ ((,,,,) _ _ _ _)
(<$>) @ ((,,,,) _ _ _ _)
:: Functor ((,,,,) _1 _2 _3 _4) =>
(a -> b) -> (_1, _2, _3, _4, a) -> (_1, _2, _3, _4, b)
這一棟建築明顯的比之前那棟寬,因而房間也多上很多。而隨著我走過一間間的房間,一直飄浮著我旁邊跟著的那群箭頭,隨著房間的不同,一次次的改變它們尾端爪子的形狀…
[to be continue]