iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Software Development

Functional Programming For Everyone系列 第 14

Day 14 - Functor

Introduction

在先前我們提到了 compose,並且將許多單一功能的純函式,透過 compose 成一個更強大的計算函式。

但問題就來了,當兩個函式 fg

Type Signature

f:: a -> b 
g:: c -> d

而進行 compose 或 pipe 時,其先決條件是 bc 必須為相同型別,也就是說 f 的輸出的型別要等於 g 的輸入型別才能進行 compose.

但在現實的開發環境中有很多需要處理的情境,例如錯誤處理(error handling), 副作用(side effect) 又或是 非同步計算(asynchronous computing),我們將其稱為 Effects, 而這些 Effect 都會包覆在類似 Container 的地方,那我們要如何將 Effect (也就是 Container) 裡面的值與純函式進行 compose 呢?

舉例來說,

safe 這個函式是會傳一個 Maybe Monad 出來,但要怎麼將包在 Maybe 裡面的 a (Maybe a) 跟 toUpper 這個純函式進行 compose?

這就是我們今天要來探討的主題,而 Functor 就是處理這件事的 algebraic structure.

safe :: (a -> Boolean) -> a -> Maybe a
toUpper :: String -> String

Maybe Monad 可以先想成是專門處理該運算可能會沒有值的情境,這在之後的文章回有更詳細的說明

Identity

首先來介紹我們第一個 Container, Identity 其功能很簡單,就是將一個值包覆在 Identity 內

Construction

Identity :: a -> Identity a

Implementation

首先我們就先將 Identity 的基底實作出來

const Identity = val => ({
    val,
    inspect: () => `Identity(${val})`
})

而我們通常會使用 of 將值進行包覆在相對應的 Container 中,實作也很簡單

可能有人會疑惑,為什麼要用 of 呢? 這我們會在之後的章節提到,但現在就先記住這是一個重要的概念就好~

Identity.of = x => Identity(x); 

假設現在我們要將 1 值包覆在 Identity 內,則為

Identity.of(1) // Identity(1)

示意圖

v7aSS7H.png

為什麼需要 Functor ?

就如同文章開頭所講的,而這裡就用實例來講解

想像一下目前有一值為 1 ,如果要將它引用到 add2 這個函式,只需要 add2(1) 就會得出 3

df1T2DX.png

const add2 = x => x + 2;

add2(1) // 3

但包覆在 Identity 的值 ,則沒辦法引用到一般的函式中進行運算

35c0GTJ.png

add2(Identity.of(1)) // [object Object]2

這就是為什麼需要 functor,因為 functor 知道如何將包覆的值進行運算,也就是 compose Identity 1 這個 Effect 與 add2 這個純函式

Ftwf6li.png

Identity.of(1).map(add2) // Identity(3)

什麼是 Functor?

理解完為什麼需樣 functor 後,其實就知道 Identity 是一個 functor,讓我們來看看其定義

Definition of Functor

  • map method
  • 必須符合兩種特性 (identity & composition)

Type Signature

map :: Functor f => f a ~> (a -> b) -> f b

Law

對於任何 functor u 都要符合下列兩種特性,

如果太抽象可以這樣思考 Identity 就是一種 functor,functor u 就是 Identity 1

  • Identity: u.map(x => x) === u
  • Composition: u.map(f).map(g) === u.map(x => g(f(x)))

實作 Identity 的 map method

const Identity = val => ({
    val, 
    map: f => {
        const result = f(val); // 將函式套用在val上
        return Identity(result);//  將結果包覆回 Identity
    }
})

簡寫版

const Identity = val => ({
    val, 
    map: f => Identity(f(val)),
    inspect: () => `Identity(${val})`
})

那我們就用馬上來驗證一下,Identity 的 map 有沒有符合其上述特性

Identity.of(1).map(x => x) === Identity.of(1)

Identity.of(1).map(add2).map(add3) === Identity.of(1).map(x => add3(add2(x))))

大功告成!!!

小結

未來在介紹各種不同的 ADT 的時候,可以看到幾乎每個 ADT 都是 Functor,而這章的目的就是讓大家知道為什麼會需要 Functor 以及用 Identity 這個 ADT 介紹實作方式。

感謝大家閱讀!!!

NEXT: Contravariant & Applicative

Reference

  1. Functor
  2. Mostly-Adequate - Ch8

上一篇
Day 13 - Semigroup II & Monoid
下一篇
Day 15 - Contravariant Functor
系列文
Functional Programming For Everyone30

1 則留言

1
西撒
iT邦新手 5 級 ‧ 2021-10-24 13:04:03

Functor 的解釋
很清楚

我要留言

立即登入留言