iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
1
Software Development

mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉系列 第 27

mostly:functional 第二十五章:Functor 的實體

一進門,就看到矗立在大廳中央的雕像,有點像是箭頭,又有點像是管子。臺座周圍四散著一堆各自相異,但卻都比較短的箭頭。當我隨手拿起一個箭頭想看時,雕像一側呼吸般閃滅著柔和的光,像是要我把手上的東西丟進去似的,於是我就照做了。

雕像的出口跑出另一個箭頭,跟原先的看起來很像,但其末端帶著幾個像爪子的裝飾,好像可以打開什麼東西似的。我順手多丟了幾個進雕像裡去…




串列是一種 Functor 嗎?

當然。做為具有不變動外層的結構,而可以用函式改變內容的性質的東西,串列應該是最為人所知曉的存在了。在串列上的 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。

二元組是一種 Functor 嗎?

是的。身為另一種容器的代表,二元組也是一種 functor。而當對二元組 fmap 時,該函式只會改變二元組中後面那個元素

-- Haskell 語法
(<$>) @ ((,) _) :: (a -> b) -> (_, a) -> (_, b)


fmap (+1) (1, 10) -- => (1, 11)

三元組、四元組……是一種 Functor 嗎?

是的。一樣是改變元組裡最後的那個元素

(<$>) @ ((,,) _ _)
  :: Functor ((,,) _1 _2) => 
  (a -> b) -> (_1, _2, a) -> (_1, _2, b)
  
fmap (+10) (1, 2, 3) -- => (1, 2, 13)

Maybe 是一種 Functor 嗎?

是的。如我們之前所說,Maybe 也是一種容器。而 fmap 對於裡面有值的 Just,會用接收到的函式來更動 Just 裡面的值,並包回 Just 中。但若是 Nothing,則如同沒有作用。而這是非常有用的性質

-- Haskell 語法
(<$>) @ Maybe :: (a -> b) -> Maybe a -> Maybe b

fmap (+1) (Just 1) -- => Just 2

fmap (+1) Nothing -- => Nothing

Either 是一種 Functor 嗎?

是的。而且類似 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 嗎?

是的。Sum 也可以視為一種容器,而這個容器也擁有 functor 的實體。

-- Haskell 語法
(<$>) @ Sum :: (a -> b) -> Sum a -> Sum b

fmap (* 10) (Sum 2) -- => Sum {getSum = 20}

Product, Any, All, Last, First 也是 Functor 嗎?

都 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]


上一篇
mostly:functional 第二十四章:Functor 的法則
下一篇
mostly:functional 第二十六章:升格,再一次升格,然後再…
系列文
mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉35

尚未有邦友留言

立即登入留言