在先前我们提到了 compose,并且将许多单一功能的纯函式,透过 compose 成一个更强大的计算函式。
但问题就来了,当两个函式 f
跟 g
Type Signature
f:: a -> b
g:: c -> d
而进行 compose 或 pipe 时,其先决条件是 b
与 c
必须为相同型别,也就是说 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 可以先想成是专门处理该运算可能会没有值的情境,这在之後的文章回有更详细的说明
首先来介绍我们第一个 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)
示意图
就如同文章开头所讲的,而这里就用实例来讲解
想像一下目前有一值为 1
,如果要将它引用到 add2
这个函式,只需要 add2(1)
就会得出 3
const add2 = x => x + 2;
add2(1) // 3
但包覆在 Identity 的值 ,则没办法引用到一般的函式中进行运算
add2(Identity.of(1)) // [object Object]2
这就是为什麽需要 functor,因为 functor 知道如何将包覆的值进行运算,也就是 compose Identity 1
这个 Effect 与 add2
这个纯函式
Identity.of(1).map(add2) // Identity(3)
理解完为什麽需样 functor 後,其实就知道 Identity 是一个 functor,让我们来看看其定义
map
methodidentity
& composition
)Type Signature
map :: Functor f => f a ~> (a -> b) -> f b
Law
对於任何 functor u
都要符合下列两种特性,
如果太抽象可以这样思考 Identity 就是一种 functor,functor u 就是 Identity 1
u.map(x => x) === u
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
<<: Material UI in React [ Day 28 ] Customization Component 自订组件 (part1)
延续 Docker 启动 process 的主题,因 container 即 process,因此合...
在之前的文章或是你在使用 git 的时候相信你一定有看过 branch 这个单字,但 branch ...
承上篇 基本型别(primitive):资料以纯值的形式存在。 物件型别(object):可能由零或...
今天我们来使用Armitage这工具对远端系统进行Gain Access测试 这套工具很强,使用前请...
过去我面试了不少公司的软件工程师职位,涵盖前端、後端,所以今天以Java相关职缺中常考的面试考题作为...