iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0
Software Development

Haskell 從入門到放棄系列 第 19

[Haskell 從入門到放棄] Day 19 - Applicative

  • 分享至 

  • xImage
  •  

繼續談 Functor

fmap (*2) (+100) 1

看到這樣的程式碼你能想的到結果是什麼嗎?好像跟我們平常在 map 時的操作不太一樣,感覺好像少了一個「可以被 map 的東西」。

先說結論上面的輸出結果是 202 ,但為什麼?還記得我們昨天討論到 functor 的意思是只要可以被 map 就代表他是 functor,那這裡被 map 的「東西」是?

用一個比較不精確的說法是我們把 (*2) map 到 (+100) 上後產生一個新的 function ,就像如果我們是用 fmap (*2) [1..5] 一樣我們是將 (*2) map 到 [1..5] 進而產生新的 list。

所以我們暫時可以把 (+100) 當作一種容器

但他鐵定不是容器,這只是方便想像的解釋。

我們把 (*2) map 到 (+100) 後應該會產生一個類似於 (*3) . (+100) 的東西。

再次提醒這只是為了方便想像的舉例。

:t fmap
-- fmap :: Functor f => (a -> b) -> f a -> f b

:t  fmap (*2)
-- fmap (*2) :: (Functor f, Num b) => f b -> f b

:t  fmap (*2) (+100) 
-- fmap (*2) (+100) :: Num b => b -> b

會看到 fmap (*2) 接受一個 Functor f wrap住的 Num b

fmap (*2) (+100) 變成 Num b => b -> b

先想一下 (a -> b) -> f a -> f b(a -> b) -> (f a -> f b) 其實是等價,所以當我們傳入兩個 function 給 fmap 時,最後的 type 就會是 Num b => b -> b

這個技巧又叫做 lifting

Applicative

我們目前都操作 functor 時所傳入的函數都是一個參數的,但如果我想要 map 時可以有兩個參數呢?

foo = fmap (*) [1..5]
:t foo
-- foo :: (Num a, Enum a) => [a -> a]

像是上面的程式碼,這邊會看到我們把 * 傳入 fmap 第一個參數 (a→b) 中,然後把他 map 到 list 裡

可以想像成有一個 list 可能去長這樣 [1* , 2*, 3*, 4* 5*]

這當然是為了方便想像的範例。

然後我再次 fmap

fmap (\f -> f 2) foo -- [2,4,6,8,10]

這邊可以想像成我們用將 [1* , 2*, 3*, 4* 5*] 取出來逐一 apply 給 2

那如果換成今天我想要將 Just (3 *) map 到 Just (5) 呢?當然我們還是能在第二次 fmap 時利用 pattern matching 匹配出 Just 然後在 map 但感覺好像有點麻煩。

所以這類有關 functor 的進階操作就是交給 Applicative

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b

首先看到這個typeclass 的型別約束已經說明了如果他是 Applicative 那它也必須是 Functor

然後會看到兩個 function pure(<*>)pure 主要是傳入一個數值 a 並會回傳被 Applicative f wrap 住的 a,而<*> 看起來有點像是 fmap 但他是接受一個裝有 function 的 functor 及另外一個 functor 後回傳一個 functor,簡單解釋像是將第一個 functor 的 function 取出來map 到第二個 functor 最後回傳一個 functor 。

先回到上面的範例,我們簡單的用用看 <*>pure

import Control.Applicative

let foo =  fmap (*) [1..5]
let result = fmap (\f x -> f x) foo <*> pure 2
result -- [2,4,6,8,10]

首先我們將 (*) map 到 [1..5] 所以這時他的 type 為 [a→a]
接下來我們再將 (\f x -> f x) map 到 foo 依然是 [a→a]
因為 list 是 Functor 的 instance ,所以我們可以用 <*>f (a→b) 變成了 f a → f b ,也就是我可以傳入另外一個 functor 來 map 這個 functor
最後因為這裡已經是 fa → fb ,所以當我傳入 pure 2 這個 f a的時候就會回傳 f b
也就是 [2,4,6,8,10]


寫到一半發現時間不太夠了只好把剩下的部分留到明天了,
做個簡單的小結,我認為 Applicative 是一個增強版的 functor 它幫助我們可以更簡單的操作 functor,特別是兩個 functor 之間的操作。


上一篇
[Haskell 從入門到放棄] Day 18 - Functor
下一篇
[Haskell 從入門到放棄] Day 20 - Applicative (2)
系列文
Haskell 從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言