函数式编程基本概念理解一: Semigroup,Monoid
实践函数式编程有几个概念是抽象的,但也是基础的,能够正确的理解它们,决定着我们如何更好的使用函数式编程。本文以 FP-TS 为例描述下我对这些概念的理解。
文中大写字符代表一个类型(一组值的集合), 小写字符代表一个值。
Semigroup (半群)
Semigroup 是指一个类型联合一个二元操作*
,满足以下的规律:
- 对于
A
中的任何a
,b
, 通过该操作*
得到的c
也属于A
, 即a * b = c
且a,b,c
同属于类型A
. - 该操作满足结合律,即
(a * b) * c = a * (b * c)
我们就称他们为Semigroup。
比如我们知道的自然数和加法 +
就是满足这个规律的。自然数和乘法 x
也是满足的。但是自然数和减法 -
就不满足了,两个自然数相减可能是负数,不是自然数,同时减法也不满足结合律。 同样对于字符串和 +
也是满足的。布尔类型和 &&
也满足。可以称之为自然数和加法构成的半群,自然数和乘法构成的半群,字符串和 +
构成的半群,布尔类型和 &&
构成的半群。
在 FP-TS 中定义如下,操作 *
为 concat
:
interface Semigroup<A> {
concat: (x: A, y: A) => A
}
concat(concat(x, y), z) == concat(x, concat(y, z)); // true
当我们在逻辑中需要合并等功能时,考虑可否能够使用Semigroup。 它也是我们的功能可否并行执行的基础。
可以自定义Semigroup
:
const semigroupSum: Semigroup<number> = {
concat: (x, y) => x + y
}
Monoid (幺半群)
延续上面的内容,如果存在一个 Semigroup<A>
,可以找到一个唯一的一个值 e
,使其能够满足:
concat(x, e) == concat(e, x) == x
-
e
属于类型A
e
我们称之为幺元,所以可以理解 Monoid (幺半群) 为存在 幺元 的 Semigroup (半群)。
比如正整数和乘法就能构成Monoid (幺半群), 1
就是那个幺元,但是正整数和加法就不行,找不到幺元,只能称之为Semigroup (半群),因为它的幺元是 0
, 如果要自然数和加法,则可以是Monoid (幺半群) 。同理字符串和 +
里的空字符串''
是它的幺元。数组中的空数组等都可以做幺元。
在 FP-TS 中定义如下,操作 *
为 concat
:
interface Monoid<A> extends Semigroup<A> {
readonly empty: A
}
可以自定义Monoid:
/** number `Monoid` under addition */
const monoidSum: Monoid<number> = {
concat: (x, y) => x + y,
empty: 0
}
/** number `Monoid` under multiplication */
const monoidProduct: Monoid<number> = {
concat: (x, y) => x * y,
empty: 1
}
const monoidString: Monoid<string> = {
concat: (x, y) => x + y,
empty: ''
}
/** boolean monoid under conjunction */
const monoidAll: Monoid<boolean> = {
concat: (x, y) => x && y,
empty: true
}
/** boolean monoid under disjunction */
const monoidAny: Monoid<boolean> = {
concat: (x, y) => x || y,
empty: false
}
参考:
https://dev.to/gcanti/getting-started-with-fp-ts-semigroup-2mf7
https://dev.to/gcanti/getting-started-with-fp-ts-monoid-ja0