iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Software Development

Functional Programming For Everyone系列 第 18

Day 18 - Chain

yo, what's up 又看到了這張熟悉的表了,想必大家都已經知道這章要來介紹什麼了,

但在這之前先來複習一下,兩個程式 fg 如何進行 compose

Review

f g composition
pure pure compose(f, g)
effects pure(unary) f.map(g)
effects pure(n-ary) f1.map(g).ap(f2)
effects effects ?

pure & pure

Function Composition 這章,我們已經提到如何用 compose 將兩(多)個純函式進行 compose

compose

const compose = (...fns) =>
    fns.reduce(
            (acc, fn) => (...args) => acc(fn(...args)), 
            x => x
        )

用法

const f = str => str.toUpperCase();
const g = str => str.concat('!');

compose(g, f)('fp') // In math, we called g o f

// 'FP!'

pure (un-ary) & effect

而在 Functor 這章,則是介紹到 effect 如何透過 map 與純函式進行 compose,並用 Identity 這個簡單的 ADT 作為範例

map

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

用法

Identity('fp').map(f).map(g).val // 'FP!'

pure (n-ary) & effects

接下來我們又在 Apply 這章提到,如何透過 ap 去處理當純函式需放入多個 effect 作為參數的情境,同樣也用了 Identity 作為範例,並實作 helper function lift2

ap

const Identity = val => ({
    val, 
    map: f => Identity(f(val)),
    ap: function (o) {
        return this.map(o.val);
    },
    inspect: () => `Identity(${val})`
})

Identity.of = x => Identity(x)

helper function lift2,若 g 的參數長度為 2

const lift2 = g => f1 => f2 => f2.ap(f1.map(g));

g 一定要是 curried 函式

用法

const concat = R.curry((x, y) => x.concat(y))

lift2(concat)(Identity.of('FP'))(Identity.of('!')).val // 'FP!'

effect & effect

如果現在 fg 都是 effect 時

f :: a -> f a
g :: b -> f b

如果是上面的 signature 要如何進行 compose 呢,這就是本章要來介紹的 chain

假設我們目前有 toUpper 與 exclaim 這兩個函式,但其回傳值皆是包覆在 Identity

// toUpper :: a -> Identity a
const toUpper = str => Identity.of(str.toUpperCase())
const exclaim = str => Identity.of(str.concat('!'))

首先,我們可以想像要怎麼將 toUpper 套用在 Identity.of('fp')

const result = Identity.of("fp")
    .map(str => toUpper(str)) // Identity(Identity('FP'))
   

可以看到此結構已經是 nested 的結構,如何再將 exclaim 套用在該值

result
 .map(wrappedStr => 
        wrappedStr.map(upperStr => exclaim(upperStr)) // Identity(Identity(Identity('FP!')))
    ) 

看一下目前的結構 Identity(Identity(Identity('FP!'))) 更深層了呢!!

如果要將值取出來我們需要

result.val.val.val

跟之前在 Apply 那章遇到的問題一樣,那這要如何解決呢?

Introduction

要解決上面的問題其實不難,就是我們需要有一個 method 是專門打平 (flatten) 包覆住的值,也就是我們只需要將其維持在一層的結構,如何實踐呢?

join

讓我們來實作 join 這個 method

const Identity = val => ({
    val, 
    map: f => Identity(f(val)),
    ap: function (o) {
        return this.map(o.val);
    },
    join: () => val,
    inspect: () => `Identity(${val})`
})

Identity.of = x => Identity(x)

join 做的事情非常簡單,就是打平結構

Identity.of("fp")
    .map(toUpper)
    .join()
    .map(exclaim)
    .join()
    .val // 'FP!'

看起來是解決我們的問題了,但是每次我們要對 effect 進行 compose 時都需要 map, join , map, join ... 有沒有方法可以時同時進行呢??

這就是 chain 誕生的由來了

Type Signature

chain :: Chain m => m a ~> (a -> m b) -> m b

Law

  • associativity: m.chain(f).chain(g) === m.chain(x => f(x).chain(g))

Implement

const Identity = val => ({
    val, 
    map: f => Identity(f(val)),
    ap: function (o) {
        return this.map(o.val);
    },
    join: () => val,
    chain: function (f) {
        return this.map(f).join()
    },
    inspect: () => `Identity(${val})`
})

Identity.of = x => Identity(x)

可以看到 chain 就是先 mapjoin,並要符合 associativity!

Identity.of("fp")
    .chain(toUpper)
    .chain(exclaim)
    .val // 'FP!'

// same as

Identity.of("fp").chain(x => toUpper(x).chain(exclaim)).val // 'FP!'

isn't that neat!?

小結

各位弟兄們!! 更新一下我們的 compose 小卡

f g composition
pure pure compose(f, g)
effects pure(unary) f.map(g)
effects pure(n-ary) f1.map(g).ap(f2)
effects effects m.chain(f).chain(g)

目前我們已經簡單介紹了一些常用的 Algebraic Structure,接下來我們要開始介紹一些常用的 ADTs,每種 ADT 都有它存在的意義,就像是 design pattern,都是各個大神從相似的問題中找出相通點,並針對相通點給定一個通用的解決方案整理成冊。各種 design pattern 都有適用的場景, ADT 也是一樣,但不同的是 ADT 裡的概念(Algebraic Structure) 背後都是有數學去佐證,每個 Algebraic Structure 都有 law,而這些 law 不僅幫助我們對於開發時增加信心,也確保了我們在進行 effect 的操作,後面都有數學佐證替我們背書。

前幾章主要是我們為什麼會需要此概念,它解決了什麼,但並沒有提到其背後數學的概念,也就是 category theroy,如果讀者們有想要更深入背後的原理,可以參考的課程 MIT 18.S097

題外話一下,筆者很喜歡從網路上找各式各樣的 CS 課程,而且內容都非常不錯,未來有機會在分享給大家。

感謝大家閱讀!!!

Maybe Monad

Reference

  1. mostly-adequate ch9
  2. chain

上一篇
Day 17 - Applicative
下一篇
Day 19 - Maybe Monad
系列文
Functional Programming For Everyone30

尚未有邦友留言

立即登入留言