iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
Software Development

Haskell 從入門到放棄系列 第 20

[Haskell 從入門到放棄] Day 20 - Applicative (2)

  • 分享至 

  • xImage
  •  

再談 Applicative

先看一下昨天最後的範例

import Control.Applicative

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

其實還有更簡單的寫法,或者該說更 applicative style 的寫法

pure (*) <*> [1..5] <*> pure 2

這次我們改先用 pure (*) 這時我們可以把它想像成他是一個被 Applicative 包住的 (*)

實際上他的 type 是 pure (*) :: (Applicative f, Num a) => f (a -> a -> a)

然後我們用 <*> [1..5] ,原本是 f (a -> a -> a) 經過 <*>就會變成 [ a → a ]

因為 <*> 是將 functor 裡面的 function 拿出來 apply 到另一個 functor 裡,所以原本 (a→a→a) 因為已經傳入一個參數,所以就變成了 (a → a) 然後最後 wrap 住我們的 functor 也就是 []

最後我們再呼叫一次 pure 2 ,一樣 pure 2 就是一個被 functor wrap 住的 2,也就只是一個單純的 (Applicative f, Num a) => f a ,所以我就可以簡單地使用 <*> 將這 f(a→b) apply 到另外一個 functor 上,所以最後就是 [2,4,6,8,10]

用比較不精確的描述的話大概會像是我們第一次使用 <*> 時是將這個 list 變成 [1*, 2* .. 5*] 的形式

當然這只是為了方便解釋,而且它可能會更像 [(*) 1 , (*)2..] 的形式

但我們現在獲得一個 f (a→b) 顯然的我無法直接讓他跟 2 進行運算,所以我就必須使用 pure 2 讓它也被 functor wrap 。之後我就可以直接使用 <*> 讓這兩個 functor 可以進行運算。

Maybe

昨天我們有提到如果是想要 Maybe 相加該怎麼辦呢?感覺就算我們不用 Applicative 好像也能達成,而且寫起來感覺沒有 list 複雜

addMaybe :: Maybe Int -> Maybe Int -> Maybe Int
addMaybe Nothing _ = Nothing
addMaybe _ Nothing = Nothing
addMaybe (Just x) (Just y) = Just (x + y)

我們先用我們的老朋友 pattern matching 試著操作看看,這裡很簡單的就是前面兩個就是匹配到任一個 Nothing 時回傳值就會是 Nothing ,而最後一行則就是當我們匹配到兩個 Just 時,就將 Just 裡的 value 取出來相加後再用 Just wrap 住。

這樣寫的缺點顯而易見,我們針對不同的 functor 就可能要寫出不一樣的 function ,針對不一樣的運算也必須寫出不一樣的 function 。

所以我們一樣可以使用 Applicative 優雅地操作 Maybe

Just (+1) <*> Just 2  -- Just 3
(+) <$> Just 1 <*> Just 2 -- Just 3
pure (+) <*> Just 1 <*> Just 2 -- Just 3
pure (+) <*> Just 1 <*> pure 2 -- Just 3
pure (+) <*> Just 1 <*> Nothing -- Nothing

首先第一行很簡單,Just (+1) 就是 f (a→b) 所以直接用 <*> apply 到 Just 2

第二行的 <$> 其實就是等於 fmap 所以也就是等於 fmap (+) Just 1 ,沒錯又是一個 f (a→b)

至於後面的就有點大同小異,如果要把一個純粹的 valuable 跟 functor 進行運算就是先用 pure 讓他一樣是被 functor wrap 住,方便我們使用 <*> 來 apply 到我們要運算的 functor。

所以 Applicative 或者該說 functor 到底解決了什麽問題?我覺得這些語法或者該說 typeclass 提供了一個統一的介面處理這些「可以被 map 的東西」,那「可以被 map 的東西」到底哪裡重要了?首先 list 算是我們相當常用的資料結構,以及 Maybe 就是當我們對於某些不確定的數值時會去使用的 typeclass ,也就是不管是管理數值的資料結構或者處理例外處理,對於 Haskell 來說都是「可以被 map 的東西」。所以勢必是要提供這些介面方便開發者去操作。

雖然我覺得這可能有點倒果為因,更應該是剛好數學上的特性導致我們可以這樣利用 Applicative 及 functor


再次提醒雖然我不斷使用 「wrap」 來表達 functor f => f a 這種形式,但我只是在說明 a 處於某種 context 中,不代表它真正被什麼東西或者資料結構給 wrap 了

今天的程式碼:https://github.com/toddLiao469469/30days-for-haskell


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

1 則留言

0
shootingstar
iT邦新手 5 級 ‧ 2023-10-03 23:12:08

雖然與Applicative無關,但我們也可以利用MaybeInt加法具有Monoid的特性來換個方式實作addMaybe

import Data.Coerce (coerce)
import Data.Monoid (Sum(Sum))

addMaybe :: Maybe Int -> Maybe Int -> Maybe Int
addMaybe = coerce $ (<>) @(Maybe (Sum Int))

這絕非是在炫技

一時半刻也看不出來它的意思 xD
但這個思路還蠻有趣的,利用 Semigroup<> 來達成這件事情。
只是我有點好奇雖然 Monoid 一定會是 Semigroup,但既然是 Monoid 直覺應該會想到 mappend 為什麼這邊會是使用 <>

我要留言

立即登入留言