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

<<:  Material UI in React [ Day 28 ] Customization Component 自订组件 (part1)

>>:  [15] [烧瓶里的部落格] 05. 静态档案

要如何在 container 里运行多个 process

延续 Docker 启动 process 的主题,因 container 即 process,因此合...

【Day13】Git 版本控制 - 什麽是 branch?

在之前的文章或是你在使用 git 的时候相信你一定有看过 branch 这个单字,但 branch ...

[Day15] 传值或传址(下)

承上篇 基本型别(primitive):资料以纯值的形式存在。 物件型别(object):可能由零或...

Day15:今天我们来聊一下使用Parrot Security的 Armitage来取得远端系统的存取(Gain Access)权

今天我们来使用Armitage这工具对远端系统进行Gain Access测试 这套工具很强,使用前请...

Day28 - Java常见面试考题

过去我面试了不少公司的软件工程师职位,涵盖前端、後端,所以今天以Java相关职缺中常考的面试考题作为...